<exclude-pattern>*/maintenance/storage/recompressTracked\.php</exclude-pattern>
<exclude-pattern>*/maintenance/storage/trackBlobs\.php</exclude-pattern>
<!-- Skip violations in some tests for now -->
+ <exclude-pattern>*/tests/phpunit/unit/includes/GlobalFunctions/*\.php</exclude-pattern>
<exclude-pattern>*/tests/phpunit/includes/GlobalFunctions/*\.php</exclude-pattern>
<exclude-pattern>*/tests/phpunit/maintenance/*\.php</exclude-pattern>
<exclude-pattern>*/tests/phpunit/integration/includes/GlobalFunctions/*\.php</exclude-pattern>
wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" );
// Load extensions/skins present in filesystem so that classes can be discovered.
$directoryToJsonMap = [
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfAppendQuery
- */
-class WfAppendQueryTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideAppendQuery
- */
- public function testAppendQuery( $url, $query, $expected, $message = null ) {
- $this->assertEquals( $expected, wfAppendQuery( $url, $query ), $message );
- }
-
- public static function provideAppendQuery() {
- return [
- [
- 'http://www.example.org/index.php',
- '',
- 'http://www.example.org/index.php',
- 'No query'
- ],
- [
- 'http://www.example.org/index.php',
- [ 'foo' => 'bar' ],
- 'http://www.example.org/index.php?foo=bar',
- 'Set query array'
- ],
- [
- 'http://www.example.org/index.php?foz=baz',
- 'foo=bar',
- 'http://www.example.org/index.php?foz=baz&foo=bar',
- 'Set query string'
- ],
- [
- 'http://www.example.org/index.php?foo=bar',
- '',
- 'http://www.example.org/index.php?foo=bar',
- 'Empty string with query'
- ],
- [
- 'http://www.example.org/index.php?foo=bar',
- [ 'baz' => 'quux' ],
- 'http://www.example.org/index.php?foo=bar&baz=quux',
- 'Add query array'
- ],
- [
- 'http://www.example.org/index.php?foo=bar',
- 'baz=quux',
- 'http://www.example.org/index.php?foo=bar&baz=quux',
- 'Add query string'
- ],
- [
- 'http://www.example.org/index.php?foo=bar',
- [ 'baz' => 'quux', 'foo' => 'baz' ],
- 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
- 'Modify query array'
- ],
- [
- 'http://www.example.org/index.php?foo=bar',
- 'baz=quux&foo=baz',
- 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
- 'Modify query string'
- ],
- [
- 'http://www.example.org/index.php#baz',
- 'foo=bar',
- 'http://www.example.org/index.php?foo=bar#baz',
- 'URL with fragment'
- ],
- [
- 'http://www.example.org/index.php?foo=bar#baz',
- 'quux=blah',
- 'http://www.example.org/index.php?foo=bar&quux=blah#baz',
- 'URL with query string and fragment'
- ]
- ];
- }
-}
+++ /dev/null
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfArrayPlus2d
- */
-class WfArrayPlus2dTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideArrays
- */
- public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) {
- $this->assertEquals(
- $expected,
- wfArrayPlus2d( $baseArray, $newValues ),
- $testName
- );
- }
-
- /**
- * Provider for testing wfArrayPlus2d
- *
- * @return array
- */
- public static function provideArrays() {
- return [
- // target array, new values array, expected result
- [
- [ 0 => '1dArray' ],
- [ 1 => '1dArray' ],
- [ 0 => '1dArray', 1 => '1dArray' ],
- "Test simple union of two arrays with different keys",
- ],
- [
- [
- 0 => [ 0 => '2dArray' ],
- ],
- [
- 0 => [ 1 => '2dArray' ],
- ],
- [
- 0 => [ 0 => '2dArray', 1 => '2dArray' ],
- ],
- "Test union of 2d arrays with different keys in the value array",
- ],
- [
- [
- 0 => [ 0 => '2dArray' ],
- ],
- [
- 0 => [ 0 => '1dArray' ],
- ],
- [
- 0 => [ 0 => '2dArray' ],
- ],
- "Test union of 2d arrays with same keys in the value array",
- ],
- [
- [
- 0 => [ 0 => [ 0 => '3dArray' ] ],
- ],
- [
- 0 => [ 0 => [ 1 => '2dArray' ] ],
- ],
- [
- 0 => [ 0 => [ 0 => '3dArray' ] ],
- ],
- "Test union of 3d array with different keys",
- ],
- [
- [
- 0 => [ 0 => [ 0 => '3dArray' ] ],
- ],
- [
- 0 => [ 1 => [ 0 => '2dArray' ] ],
- ],
- [
- 0 => [ 0 => [ 0 => '3dArray' ], 1 => [ 0 => '2dArray' ] ],
- ],
- "Test union of 3d array with different keys in the value array",
- ],
- [
- [
- 0 => [ 0 => [ 0 => '3dArray' ] ],
- ],
- [
- 0 => [ 0 => [ 0 => '2dArray' ] ],
- ],
- [
- 0 => [ 0 => [ 0 => '3dArray' ] ],
- ],
- "Test union of 3d array with same keys in the value array",
- ],
- ];
- }
-}
+++ /dev/null
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfAssembleUrl
- */
-class WfAssembleUrlTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideURLParts
- */
- public function testWfAssembleUrl( $parts, $output ) {
- $partsDump = print_r( $parts, true );
- $this->assertEquals(
- $output,
- wfAssembleUrl( $parts ),
- "Testing $partsDump assembles to $output"
- );
- }
-
- /**
- * Provider of URL parts for testing wfAssembleUrl()
- *
- * @return array
- */
- public static function provideURLParts() {
- $schemes = [
- '' => [],
- '//' => [
- 'delimiter' => '//',
- ],
- 'http://' => [
- 'scheme' => 'http',
- 'delimiter' => '://',
- ],
- ];
-
- $hosts = [
- '' => [],
- 'example.com' => [
- 'host' => 'example.com',
- ],
- 'example.com:123' => [
- 'host' => 'example.com',
- 'port' => 123,
- ],
- 'id@example.com' => [
- 'user' => 'id',
- 'host' => 'example.com',
- ],
- 'id@example.com:123' => [
- 'user' => 'id',
- 'host' => 'example.com',
- 'port' => 123,
- ],
- 'id:key@example.com' => [
- 'user' => 'id',
- 'pass' => 'key',
- 'host' => 'example.com',
- ],
- 'id:key@example.com:123' => [
- 'user' => 'id',
- 'pass' => 'key',
- 'host' => 'example.com',
- 'port' => 123,
- ],
- ];
-
- $cases = [];
- foreach ( $schemes as $scheme => $schemeParts ) {
- foreach ( $hosts as $host => $hostParts ) {
- foreach ( [ '', '/path' ] as $path ) {
- foreach ( [ '', 'query' ] as $query ) {
- foreach ( [ '', 'fragment' ] as $fragment ) {
- $parts = array_merge(
- $schemeParts,
- $hostParts
- );
- $url = $scheme .
- $host .
- $path;
-
- if ( $path ) {
- $parts['path'] = $path;
- }
- if ( $query ) {
- $parts['query'] = $query;
- $url .= '?' . $query;
- }
- if ( $fragment ) {
- $parts['fragment'] = $fragment;
- $url .= '#' . $fragment;
- }
-
- $cases[] = [
- $parts,
- $url,
- ];
- }
- }
- }
- }
- }
-
- $complexURL = 'http://id:key@example.org:321' .
- '/over/there?name=ferret&foo=bar#nose';
- $cases[] = [
- wfParseUrl( $complexURL ),
- $complexURL,
- ];
-
- return $cases;
- }
-}
+++ /dev/null
-<?php
-/**
- * @group GlobalFunctions
- * @covers ::wfBaseName
- */
-class WfBaseNameTest extends MediaWikiTestCase {
- /**
- * @dataProvider providePaths
- */
- public function testBaseName( $fullpath, $basename ) {
- $this->assertEquals( $basename, wfBaseName( $fullpath ),
- "wfBaseName('$fullpath') => '$basename'" );
- }
-
- public static function providePaths() {
- return [
- [ '', '' ],
- [ '/', '' ],
- [ '\\', '' ],
- [ '//', '' ],
- [ '\\\\', '' ],
- [ 'a', 'a' ],
- [ 'aaaa', 'aaaa' ],
- [ '/a', 'a' ],
- [ '\\a', 'a' ],
- [ '/aaaa', 'aaaa' ],
- [ '\\aaaa', 'aaaa' ],
- [ '/aaaa/', 'aaaa' ],
- [ '\\aaaa\\', 'aaaa' ],
- [ '\\aaaa\\', 'aaaa' ],
- [
- '/mnt/upload3/wikipedia/en/thumb/8/8b/'
- . 'Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
- '93px-Zork_Grand_Inquisitor_box_cover.jpg'
- ],
- [ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ],
- [ 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ],
- ];
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfEscapeShellArg
- */
-class WfEscapeShellArgTest extends MediaWikiTestCase {
- public function testSingleInput() {
- if ( wfIsWindows() ) {
- $expected = '"blah"';
- } else {
- $expected = "'blah'";
- }
-
- $actual = wfEscapeShellArg( 'blah' );
-
- $this->assertEquals( $expected, $actual );
- }
-
- public function testMultipleArgs() {
- if ( wfIsWindows() ) {
- $expected = '"foo" "bar" "baz"';
- } else {
- $expected = "'foo' 'bar' 'baz'";
- }
-
- $actual = wfEscapeShellArg( 'foo', 'bar', 'baz' );
-
- $this->assertEquals( $expected, $actual );
- }
-
- public function testMultipleArgsAsArray() {
- if ( wfIsWindows() ) {
- $expected = '"foo" "bar" "baz"';
- } else {
- $expected = "'foo' 'bar' 'baz'";
- }
-
- $actual = wfEscapeShellArg( [ 'foo', 'bar', 'baz' ] );
-
- $this->assertEquals( $expected, $actual );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfGetCaller
- */
-class WfGetCallerTest extends MediaWikiTestCase {
- public function testZero() {
- $this->assertEquals( 'WfGetCallerTest->testZero', wfGetCaller( 1 ) );
- }
-
- function callerOne() {
- return wfGetCaller();
- }
-
- public function testOne() {
- $this->assertEquals( 'WfGetCallerTest->testOne', self::callerOne() );
- }
-
- static function intermediateFunction( $level = 2, $n = 0 ) {
- if ( $n > 0 ) {
- return self::intermediateFunction( $level, $n - 1 );
- }
-
- return wfGetCaller( $level );
- }
-
- public function testTwo() {
- $this->assertEquals( 'WfGetCallerTest->testTwo', self::intermediateFunction() );
- }
-
- public function testN() {
- $this->assertEquals( 'WfGetCallerTest->testN', self::intermediateFunction( 2, 0 ) );
- $this->assertEquals(
- 'WfGetCallerTest::intermediateFunction',
- self::intermediateFunction( 1, 0 )
- );
-
- for ( $i = 0; $i < 10; $i++ ) {
- $this->assertEquals(
- 'WfGetCallerTest::intermediateFunction',
- self::intermediateFunction( $i + 1, $i )
- );
- }
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfRemoveDotSegments
- */
-class WfRemoveDotSegmentsTest extends MediaWikiTestCase {
- /**
- * @dataProvider providePaths
- */
- public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
- $this->assertEquals(
- $outputPath,
- wfRemoveDotSegments( $inputPath ),
- "Testing $inputPath expands to $outputPath"
- );
- }
-
- /**
- * Provider of URL paths for testing wfRemoveDotSegments()
- *
- * @return array
- */
- public static function providePaths() {
- return [
- [ '/a/b/c/./../../g', '/a/g' ],
- [ 'mid/content=5/../6', 'mid/6' ],
- [ '/a//../b', '/a/b' ],
- [ '/.../a', '/.../a' ],
- [ '.../a', '.../a' ],
- [ '', '' ],
- [ '/', '/' ],
- [ '//', '//' ],
- [ '.', '' ],
- [ '..', '' ],
- [ '...', '...' ],
- [ '/.', '/' ],
- [ '/..', '/' ],
- [ './', '' ],
- [ '../', '' ],
- [ './a', 'a' ],
- [ '../a', 'a' ],
- [ '../../a', 'a' ],
- [ '.././a', 'a' ],
- [ './../a', 'a' ],
- [ '././a', 'a' ],
- [ '../../', '' ],
- [ '.././', '' ],
- [ './../', '' ],
- [ '././', '' ],
- [ '../..', '' ],
- [ '../.', '' ],
- [ './..', '' ],
- [ './.', '' ],
- [ '/../../a', '/a' ],
- [ '/.././a', '/a' ],
- [ '/./../a', '/a' ],
- [ '/././a', '/a' ],
- [ '/../../', '/' ],
- [ '/.././', '/' ],
- [ '/./../', '/' ],
- [ '/././', '/' ],
- [ '/../..', '/' ],
- [ '/../.', '/' ],
- [ '/./..', '/' ],
- [ '/./.', '/' ],
- [ 'b/../../a', '/a' ],
- [ 'b/.././a', '/a' ],
- [ 'b/./../a', '/a' ],
- [ 'b/././a', 'b/a' ],
- [ 'b/../../', '/' ],
- [ 'b/.././', '/' ],
- [ 'b/./../', '/' ],
- [ 'b/././', 'b/' ],
- [ 'b/../..', '/' ],
- [ 'b/../.', '/' ],
- [ 'b/./..', '/' ],
- [ 'b/./.', 'b/' ],
- [ '/b/../../a', '/a' ],
- [ '/b/.././a', '/a' ],
- [ '/b/./../a', '/a' ],
- [ '/b/././a', '/b/a' ],
- [ '/b/../../', '/' ],
- [ '/b/.././', '/' ],
- [ '/b/./../', '/' ],
- [ '/b/././', '/b/' ],
- [ '/b/../..', '/' ],
- [ '/b/../.', '/' ],
- [ '/b/./..', '/' ],
- [ '/b/./.', '/b/' ],
- ];
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfShorthandToInteger
- */
-class WfShorthandToIntegerTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideABunchOfShorthands
- */
- public function testWfShorthandToInteger( $input, $output, $description ) {
- $this->assertEquals(
- wfShorthandToInteger( $input ),
- $output,
- $description
- );
- }
-
- public static function provideABunchOfShorthands() {
- return [
- [ '', -1, 'Empty string' ],
- [ ' ', -1, 'String of spaces' ],
- [ '1G', 1024 * 1024 * 1024, 'One gig uppercased' ],
- [ '1g', 1024 * 1024 * 1024, 'One gig lowercased' ],
- [ '1M', 1024 * 1024, 'One meg uppercased' ],
- [ '1m', 1024 * 1024, 'One meg lowercased' ],
- [ '1K', 1024, 'One kb uppercased' ],
- [ '1k', 1024, 'One kb lowercased' ],
- ];
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfStringToBool
- */
-class WfStringToBoolTest extends MediaWikiTestCase {
-
- public function getTestCases() {
- return [
- [ 'true', true ],
- [ 'on', true ],
- [ 'yes', true ],
- [ 'TRUE', true ],
- [ 'YeS', true ],
- [ 'On', true ],
- [ '1', true ],
- [ '+1', true ],
- [ '01', true ],
- [ '-001', true ],
- [ ' 1', true ],
- [ '-1 ', true ],
- [ '', false ],
- [ '0', false ],
- [ 'false', false ],
- [ 'NO', false ],
- [ 'NOT', false ],
- [ 'never', false ],
- [ '!&', false ],
- [ '-0', false ],
- [ '+0', false ],
- [ 'forget about it', false ],
- [ ' on', false ],
- [ 'true ', false ],
- ];
- }
-
- /**
- * @dataProvider getTestCases
- * @param string $str
- * @param bool $bool
- */
- public function testStr2Bool( $str, $bool ) {
- if ( $bool ) {
- $this->assertTrue( wfStringToBool( $str ) );
- } else {
- $this->assertFalse( wfStringToBool( $str ) );
- }
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @group GlobalFunctions
- * @covers ::wfTimestamp
- */
-class WfTimestampTest extends MediaWikiTestCase {
- /**
- * @dataProvider provideNormalTimestamps
- */
- public function testNormalTimestamps( $input, $format, $output, $desc ) {
- $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
- }
-
- public static function provideNormalTimestamps() {
- $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
-
- return [
- // TS_UNIX
- [ $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ],
- [ -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ],
- [ $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ],
- [ $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ],
- [ $t + 0.01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ],
-
- [ $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ],
-
- // TS_MW
- [ '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ],
- [ '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ],
- [ '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ],
- [ '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ],
-
- // TS_DB
- [ '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ],
- [ '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ],
- [ '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ],
- [
- '2001-01-15 12:34:56',
- TS_ISO_8601_BASIC,
- '20010115T123456Z',
- 'TS_DB to TS_ISO_8601_BASIC'
- ],
-
- # rfc2822 section 3.3
- [ '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ],
- [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
- [
- ' Mon, 15 Jan 2001 12:34:56 GMT',
- TS_MW,
- '20010115123456',
- 'TS_RFC2822 with leading space to TS_MW'
- ],
- [
- '15 Jan 2001 12:34:56 GMT',
- TS_MW,
- '20010115123456',
- 'TS_RFC2822 without optional day-of-week to TS_MW'
- ],
-
- # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
- # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
- [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
-
- # WSP = SP / HTAB ; rfc2234
- [
- "Mon, 15 Jan\x092001 12:34:56 GMT",
- TS_MW,
- '20010115123456',
- 'TS_RFC2822 with HTAB to TS_MW'
- ],
- [
- "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT",
- TS_MW,
- '20010115123456',
- 'TS_RFC2822 with HTAB and SP to TS_MW'
- ],
- [
- 'Sun, 6 Nov 94 08:49:37 GMT',
- TS_MW,
- '19941106084937',
- 'TS_RFC2822 with obsolete year to TS_MW'
- ],
- ];
- }
-
- /**
- * This test checks wfTimestamp() with values outside.
- * It needs PHP 64 bits or PHP > 5.1.
- * See r74778 and T27451
- * @dataProvider provideOldTimestamps
- */
- public function testOldTimestamps( $input, $outputType, $output, $message ) {
- $timestamp = wfTimestamp( $outputType, $input );
- if ( substr( $output, 0, 1 ) === '/' ) {
- // T66946: Day of the week calculations for very old
- // timestamps varies from system to system.
- $this->assertRegExp( $output, $timestamp, $message );
- } else {
- $this->assertEquals( $output, $timestamp, $message );
- }
- }
-
- public static function provideOldTimestamps() {
- return [
- [
- '19011213204554',
- TS_RFC2822,
- 'Fri, 13 Dec 1901 20:45:54 GMT',
- 'Earliest time according to PHP documentation'
- ],
- [ '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ],
- [ '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ],
- [ '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ],
- [ '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ],
- [
- '19011213204551',
- TS_RFC2822,
- 'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1'
- ],
- [ '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ],
- [ '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ],
- [ '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ],
- [ '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ],
- [ '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ],
- [ '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ],
- [
- '0117-08-09 12:34:56',
- TS_RFC2822,
- '/, 09 Aug 0117 12:34:56 GMT$/',
- 'Death of Roman Emperor [[Trajan]]'
- ],
-
- /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
- [ '-58979923200', TS_RFC2822, '/, 01 Jan 0101 00:00:00 GMT$/', '1/1/101' ],
- [ '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ],
-
- /* It is not clear if we should generate a year 0 or not
- * We are completely off RFC2822 requirement of year being
- * 1900 or later.
- */
- [
- '-62142076800',
- TS_RFC2822,
- 'Wed, 18 Oct 0000 00:00:00 GMT',
- 'ISO 8601:2004 [[year 0]], also called [[1 BC]]'
- ],
- ];
- }
-
- /**
- * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
- * @dataProvider provideHttpDates
- */
- public function testHttpDate( $input, $output, $desc ) {
- $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
- }
-
- public static function provideHttpDates() {
- return [
- [ 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ],
- [ 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ],
- [ 'Sun Nov 6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ],
- // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
- [
- 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626',
- '20101122141242',
- 'Netscape extension to HTTP/1.0'
- ],
- ];
- }
-
- /**
- * There are a number of assumptions in our codebase where wfTimestamp()
- * should give the current date but it is not given a 0 there. See r71751 CR
- */
- public function testTimestampParameter() {
- $now = wfTimestamp( TS_UNIX );
- // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
- // for the cases where the test is run in a second boundary.
-
- $zero = wfTimestamp( TS_UNIX, 0 );
- $this->assertNotEquals( false, $zero );
- $this->assertLessThan( 5, $zero - $now );
-
- $empty = wfTimestamp( TS_UNIX, '' );
- $this->assertNotEquals( false, $empty );
- $this->assertLessThan( 5, $empty - $now );
-
- $null = wfTimestamp( TS_UNIX, null );
- $this->assertNotEquals( false, $null );
- $this->assertLessThan( 5, $null - $now );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * The function only need a string parameter and might react to IIS7.0
- *
- * @group GlobalFunctions
- * @covers ::wfUrlencode
- */
-class WfUrlencodeTest extends MediaWikiTestCase {
- # ### TESTS ##############################################################
-
- /**
- * @dataProvider provideURLS
- */
- public function testEncodingUrlWith( $input, $expected ) {
- $this->verifyEncodingFor( 'Apache', $input, $expected );
- }
-
- /**
- * @dataProvider provideURLS
- */
- public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
- $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
- }
-
- # ### HELPERS #############################################################
-
- /**
- * Internal helper that actually run the test.
- * Called by the public methods testEncodingUrlWith...()
- */
- private function verifyEncodingFor( $server, $input, $expectations ) {
- $expected = $this->extractExpect( $server, $expectations );
-
- // save up global
- $old = $_SERVER['SERVER_SOFTWARE'] ?? null;
- $_SERVER['SERVER_SOFTWARE'] = $server;
- wfUrlencode( null );
-
- // do the requested test
- $this->assertEquals(
- $expected,
- wfUrlencode( $input ),
- "Encoding '$input' for server '$server' should be '$expected'"
- );
-
- // restore global
- if ( $old === null ) {
- unset( $_SERVER['SERVER_SOFTWARE'] );
- } else {
- $_SERVER['SERVER_SOFTWARE'] = $old;
- }
- wfUrlencode( null );
- }
-
- /**
- * Interprets the provider array. Return expected value depending
- * the HTTP server name.
- */
- private function extractExpect( $server, $expectations ) {
- if ( is_string( $expectations ) ) {
- return $expectations;
- } elseif ( is_array( $expectations ) ) {
- if ( !array_key_exists( $server, $expectations ) ) {
- throw new MWException( __METHOD__ . " expectation does not have any "
- . "value for server name $server. Check the provider array.\n" );
- } else {
- return $expectations[$server];
- }
- } else {
- throw new MWException( __METHOD__ . " given invalid expectation for "
- . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
- }
- }
-
- # ### PROVIDERS ###########################################################
-
- /**
- * Format is either:
- * [ 'input', 'expected' ];
- * Or:
- * [ 'input',
- * [ 'Apache', 'expected' ],
- * [ 'Microsoft-IIS/7', 'expected' ],
- * ],
- * If you want to add other HTTP server name, you will have to add a new
- * testing method much like the testEncodingUrlWith() method above.
- */
- public static function provideURLS() {
- return [
- # ## RFC 1738 chars
- // + is not safe
- [ '+', '%2B' ],
- // & and = not safe in queries
- [ '&', '%26' ],
- [ '=', '%3D' ],
-
- [ ':', [
- 'Apache' => ':',
- 'Microsoft-IIS/7' => '%3A',
- ] ],
-
- // remaining chars do not need encoding
- [
- ';@$-_.!*',
- ';@$-_.!*',
- ],
-
- # ## Other tests
- // slash remain unchanged. %2F seems to break things
- [ '/', '/' ],
- // T105265
- [ '~', '~' ],
-
- // Other 'funnies' chars
- [ '[]', '%5B%5D' ],
- [ '<>', '%3C%3E' ],
-
- // Apostrophe is encoded
- [ '\'', '%27' ],
- ];
- }
-}
+++ /dev/null
-<?php
-
-/**
- * Tests for the PathRouter parsing.
- *
- * @covers PathRouter
- */
-class PathRouterTest extends MediaWikiTestCase {
-
- /**
- * @var PathRouter
- */
- protected $basicRouter;
-
- protected function setUp() {
- parent::setUp();
- $router = new PathRouter;
- $router->add( "/wiki/$1" );
- $this->basicRouter = $router;
- }
-
- public static function provideParse() {
- $tests = [
- // Basic path parsing
- 'Basic path parsing' => [
- "/wiki/$1",
- "/wiki/Foo",
- [ 'title' => "Foo" ]
- ],
- //
- 'Loose path auto-$1: /$1' => [
- "/",
- "/Foo",
- [ 'title' => "Foo" ]
- ],
- 'Loose path auto-$1: /wiki' => [
- "/wiki",
- "/wiki/Foo",
- [ 'title' => "Foo" ]
- ],
- 'Loose path auto-$1: /wiki/' => [
- "/wiki/",
- "/wiki/Foo",
- [ 'title' => "Foo" ]
- ],
- // Ensure that path is based on specificity, not order
- 'Order, /$1 added first' => [
- [ "/$1", "/a/$1", "/b/$1" ],
- "/a/Foo",
- [ 'title' => "Foo" ]
- ],
- 'Order, /$1 added last' => [
- [ "/b/$1", "/a/$1", "/$1" ],
- "/a/Foo",
- [ 'title' => "Foo" ]
- ],
- // Handling of key based arrays with a url parameter
- 'Key based array' => [
- [ [
- 'path' => [ 'edit' => "/edit/$1" ],
- 'params' => [ 'action' => '$key' ],
- ] ],
- "/edit/Foo",
- [ 'title' => "Foo", 'action' => 'edit' ]
- ],
- // Additional parameter
- 'Basic $2' => [
- [ [
- 'path' => '/$2/$1',
- 'params' => [ 'test' => '$2' ]
- ] ],
- "/asdf/Foo",
- [ 'title' => "Foo", 'test' => 'asdf' ]
- ],
- ];
- // Shared patterns for restricted value parameter tests
- $restrictedPatterns = [
- [
- 'path' => '/$2/$1',
- 'params' => [ 'test' => '$2' ],
- 'options' => [ '$2' => [ 'a', 'b' ] ]
- ],
- [
- 'path' => '/$2/$1',
- 'params' => [ 'test2' => '$2' ],
- 'options' => [ '$2' => 'c' ]
- ],
- '/$1'
- ];
- $tests += [
- // Restricted value parameter tests
- 'Restricted 1' => [
- $restrictedPatterns,
- "/asdf/Foo",
- [ 'title' => "asdf/Foo" ]
- ],
- 'Restricted 2' => [
- $restrictedPatterns,
- "/a/Foo",
- [ 'title' => "Foo", 'test' => 'a' ]
- ],
- 'Restricted 3' => [
- $restrictedPatterns,
- "/c/Foo",
- [ 'title' => "Foo", 'test2' => 'c' ]
- ],
-
- // Callback test
- 'Callback' => [
- [ [
- 'path' => "/$1",
- 'params' => [ 'a' => 'b', 'data:foo' => 'bar' ],
- 'options' => [ 'callback' => [ __CLASS__, 'callbackForTest' ] ]
- ] ],
- '/Foo',
- [
- 'title' => "Foo",
- 'x' => 'Foo',
- 'a' => 'b',
- 'foo' => 'bar'
- ]
- ],
-
- // Test to ensure that matches are not made if a parameter expects nonexistent input
- 'Fail' => [
- [ [
- 'path' => "/wiki/$1",
- 'params' => [ 'title' => "$1$2" ],
- ] ],
- "/wiki/A",
- []
- ],
-
- // Make sure the router handles titles like Special:Recentchanges correctly
- 'Special title' => [
- "/wiki/$1",
- "/wiki/Special:Recentchanges",
- [ 'title' => "Special:Recentchanges" ]
- ],
-
- // Make sure the router decodes urlencoding properly
- 'URL encoding' => [
- "/wiki/$1",
- "/wiki/Title_With%20Space",
- [ 'title' => "Title_With Space" ]
- ],
-
- // Double slash and dot expansion
- 'Double slash in prefix' => [
- '/wiki/$1',
- '//wiki/Foo',
- [ 'title' => 'Foo' ]
- ],
- 'Double slash at start of $1' => [
- '/wiki/$1',
- '/wiki//Foo',
- [ 'title' => '/Foo' ]
- ],
- 'Double slash in middle of $1' => [
- '/wiki/$1',
- '/wiki/.hack//SIGN',
- [ 'title' => '.hack//SIGN' ]
- ],
- 'Dots removed 1' => [
- '/wiki/$1',
- '/x/../wiki/Foo',
- [ 'title' => 'Foo' ]
- ],
- 'Dots removed 2' => [
- '/wiki/$1',
- '/./wiki/Foo',
- [ 'title' => 'Foo' ]
- ],
- 'Dots retained 1' => [
- '/wiki/$1',
- '/wiki/../wiki/Foo',
- [ 'title' => '../wiki/Foo' ]
- ],
- 'Dots retained 2' => [
- '/wiki/$1',
- '/wiki/./Foo',
- [ 'title' => './Foo' ]
- ],
- 'Triple slash' => [
- '/wiki/$1',
- '///wiki/Foo',
- [ 'title' => 'Foo' ]
- ],
- // '..' only traverses one slash, see e.g. RFC 3986
- 'Dots traversing double slash 1' => [
- '/wiki/$1',
- '/a//b/../../wiki/Foo',
- []
- ],
- 'Dots traversing double slash 2' => [
- '/wiki/$1',
- '/a//b/../../../wiki/Foo',
- [ 'title' => 'Foo' ]
- ],
- ];
-
- // Make sure the router doesn't break on special characters like $ used in regexp replacements
- foreach ( [ "$", "$1", "\\", "\\$1" ] as $char ) {
- $tests["Regexp character $char"] = [
- "/wiki/$1",
- "/wiki/$char",
- [ 'title' => "$char" ]
- ];
- }
-
- $tests += [
- // Make sure the router handles characters like +&() properly
- "Special characters" => [
- "/wiki/$1",
- "/wiki/Plus+And&Dollar\\Stuff();[]{}*",
- [ 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ],
- ],
-
- // Make sure the router handles unicode characters correctly
- "Unicode 1" => [
- "/wiki/$1",
- "/wiki/Spécial:Modifications_récentes" ,
- [ 'title' => "Spécial:Modifications_récentes" ],
- ],
-
- "Unicode 2" => [
- "/wiki/$1",
- "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes",
- [ 'title' => "Spécial:Modifications_récentes" ],
- ]
- ];
-
- // Ensure the router doesn't choke on long paths.
- $lorem = "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_" .
- "tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_" .
- "nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._" .
- "Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_" .
- "eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_" .
- "in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum.";
-
- $tests += [
- "Long path" => [
- "/wiki/$1",
- "/wiki/$lorem",
- [ 'title' => $lorem ]
- ],
-
- // Ensure that the php passed site of parameter values are not urldecoded
- "Pattern urlencoding" => [
- [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => '%20:$1' ] ] ],
- "/wiki/Foo",
- [ 'title' => '%20:Foo' ]
- ],
-
- // Ensure that raw parameter values do not have any variable replacements or urldecoding
- "Raw param value" => [
- [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => [ 'value' => 'bar%20$1' ] ] ] ],
- "/wiki/Foo",
- [ 'title' => 'bar%20$1' ]
- ]
- ];
-
- return $tests;
- }
-
- /**
- * Test path parsing
- * @dataProvider provideParse
- */
- public function testParse( $patterns, $path, $expected ) {
- $patterns = (array)$patterns;
-
- $router = new PathRouter;
- foreach ( $patterns as $pattern ) {
- if ( is_array( $pattern ) ) {
- $router->add( $pattern['path'], $pattern['params'] ?? [],
- $pattern['options'] ?? [] );
- } else {
- $router->add( $pattern );
- }
- }
- $matches = $router->parse( $path );
- $this->assertEquals( $matches, $expected );
- }
-
- public static function callbackForTest( &$matches, $data ) {
- $matches['x'] = $data['$1'];
- $matches['foo'] = $data['foo'];
- }
-
- public static function provideWeight() {
- return [
- [ '/Foo', [ 'title' => 'Foo' ] ],
- [ '/Bar', [ 'ping' => 'pong' ] ],
- [ '/Baz', [ 'marco' => 'polo' ] ],
- [ '/asdf-foo', [ 'title' => 'qwerty-foo' ] ],
- [ '/qwerty-bar', [ 'title' => 'asdf-bar' ] ],
- [ '/a/Foo', [ 'title' => 'Foo' ] ],
- [ '/asdf/Foo', [ 'title' => 'Foo' ] ],
- [ '/qwerty/Foo', [ 'title' => 'Foo', 'qwerty' => 'qwerty' ] ],
- [ '/baz/Foo', [ 'title' => 'Foo', 'unrestricted' => 'baz' ] ],
- [ '/y/Foo', [ 'title' => 'Foo', 'restricted-to-y' => 'y' ] ],
- ];
- }
-
- /**
- * Test to ensure weight of paths is handled correctly
- * @dataProvider provideWeight
- */
- public function testWeight( $path, $expected ) {
- $router = new PathRouter;
- $router->addStrict( "/Bar", [ 'ping' => 'pong' ] );
- $router->add( "/asdf-$1", [ 'title' => 'qwerty-$1' ] );
- $router->add( "/$1" );
- $router->add( "/qwerty-$1", [ 'title' => 'asdf-$1' ] );
- $router->addStrict( "/Baz", [ 'marco' => 'polo' ] );
- $router->add( "/a/$1" );
- $router->add( "/asdf/$1" );
- $router->add( "/$2/$1", [ 'unrestricted' => '$2' ] );
- $router->add( [ 'qwerty' => "/qwerty/$1" ], [ 'qwerty' => '$key' ] );
- $router->add( "/$2/$1", [ 'restricted-to-y' => '$2' ], [ '$2' => 'y' ] );
-
- $this->assertEquals( $router->parse( $path ), $expected );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use ArrayIterator;
-use MediaWiki\Rest\HttpException;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWikiTestCase;
-
-/** @covers \MediaWiki\Rest\ResponseFactory */
-class ResponseFactoryTest extends MediaWikiTestCase {
- public static function provideEncodeJson() {
- return [
- [ (object)[], '{}' ],
- [ '/', '"/"' ],
- [ '£', '"£"' ],
- [ [], '[]' ],
- ];
- }
-
- /** @dataProvider provideEncodeJson */
- public function testEncodeJson( $input, $expected ) {
- $rf = new ResponseFactory;
- $this->assertSame( $expected, $rf->encodeJson( $input ) );
- }
-
- public function testCreateJson() {
- $rf = new ResponseFactory;
- $response = $rf->createJson( [] );
- $response->getBody()->rewind();
- $this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
- $this->assertSame( '[]', $response->getBody()->getContents() );
- // Make sure getSize() is functional, since testCreateNoContent() depends on it
- $this->assertSame( 2, $response->getBody()->getSize() );
- }
-
- public function testCreateNoContent() {
- $rf = new ResponseFactory;
- $response = $rf->createNoContent();
- $this->assertSame( [], $response->getHeader( 'Content-Type' ) );
- $this->assertSame( 0, $response->getBody()->getSize() );
- $this->assertSame( 204, $response->getStatusCode() );
- }
-
- public function testCreatePermanentRedirect() {
- $rf = new ResponseFactory;
- $response = $rf->createPermanentRedirect( 'http://www.example.com/' );
- $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
- $this->assertSame( 301, $response->getStatusCode() );
- }
-
- public function testCreateLegacyTemporaryRedirect() {
- $rf = new ResponseFactory;
- $response = $rf->createLegacyTemporaryRedirect( 'http://www.example.com/' );
- $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
- $this->assertSame( 302, $response->getStatusCode() );
- }
-
- public function testCreateTemporaryRedirect() {
- $rf = new ResponseFactory;
- $response = $rf->createTemporaryRedirect( 'http://www.example.com/' );
- $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
- $this->assertSame( 307, $response->getStatusCode() );
- }
-
- public function testCreateSeeOther() {
- $rf = new ResponseFactory;
- $response = $rf->createSeeOther( 'http://www.example.com/' );
- $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
- $this->assertSame( 303, $response->getStatusCode() );
- }
-
- public function testCreateNotModified() {
- $rf = new ResponseFactory;
- $response = $rf->createNotModified();
- $this->assertSame( 0, $response->getBody()->getSize() );
- $this->assertSame( 304, $response->getStatusCode() );
- }
-
- /** @expectedException \InvalidArgumentException */
- public function testCreateHttpErrorInvalid() {
- $rf = new ResponseFactory;
- $rf->createHttpError( 200 );
- }
-
- public function testCreateHttpError() {
- $rf = new ResponseFactory;
- $response = $rf->createHttpError( 415, [ 'message' => '...' ] );
- $this->assertSame( 415, $response->getStatusCode() );
- $body = $response->getBody();
- $body->rewind();
- $data = json_decode( $body->getContents(), true );
- $this->assertSame( 415, $data['httpCode'] );
- $this->assertSame( '...', $data['message'] );
- }
-
- public function testCreateFromExceptionUnlogged() {
- $rf = new ResponseFactory;
- $response = $rf->createFromException( new HttpException( 'hello', 415 ) );
- $this->assertSame( 415, $response->getStatusCode() );
- $body = $response->getBody();
- $body->rewind();
- $data = json_decode( $body->getContents(), true );
- $this->assertSame( 415, $data['httpCode'] );
- $this->assertSame( 'hello', $data['message'] );
- }
-
- public function testCreateFromExceptionLogged() {
- $rf = new ResponseFactory;
- $response = $rf->createFromException( new \Exception( "hello", 415 ) );
- $this->assertSame( 500, $response->getStatusCode() );
- $body = $response->getBody();
- $body->rewind();
- $data = json_decode( $body->getContents(), true );
- $this->assertSame( 500, $data['httpCode'] );
- $this->assertSame( 'Error: exception of type Exception', $data['message'] );
- }
-
- public static function provideCreateFromReturnValue() {
- return [
- [ 'hello', '{"value":"hello"}' ],
- [ true, '{"value":true}' ],
- [ [ 'x' => 'y' ], '{"x":"y"}' ],
- [ [ 'x', 'y' ], '["x","y"]' ],
- [ [ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
- [ (object)[ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
- [ [], '[]' ],
- [ (object)[], '{}' ],
- ];
- }
-
- /** @dataProvider provideCreateFromReturnValue */
- public function testCreateFromReturnValue( $input, $expected ) {
- $rf = new ResponseFactory;
- $response = $rf->createFromReturnValue( $input );
- $body = $response->getBody();
- $body->rewind();
- $this->assertSame( $expected, $body->getContents() );
- }
-
- /** @expectedException \InvalidArgumentException */
- public function testCreateFromReturnValueInvalid() {
- $rf = new ResponseFactory;
- $rf->createFromReturnValue( new ArrayIterator );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\MainSlotRoleHandler;
-use MediaWikiTestCase;
-use PHPUnit\Framework\MockObject\MockObject;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler
- */
-class MainSlotRoleHandlerTest extends MediaWikiTestCase {
-
- private function makeTitleObject( $ns ) {
- /** @var Title|MockObject $title */
- $title = $this->getMockBuilder( Title::class )
- ->disableOriginalConstructor()
- ->getMock();
-
- $title->method( 'getNamespace' )
- ->willReturn( $ns );
-
- return $title;
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
- */
- public function testConstruction() {
- $handler = new MainSlotRoleHandler( [] );
- $this->assertSame( 'main', $handler->getRole() );
- $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
-
- $hints = $handler->getOutputLayoutHints();
- $this->assertArrayHasKey( 'display', $hints );
- $this->assertArrayHasKey( 'region', $hints );
- $this->assertArrayHasKey( 'placement', $hints );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
- */
- public function testFetDefaultModel() {
- $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
-
- // For the main handler, the namespace determins the default model
- $titleMain = $this->makeTitleObject( NS_MAIN );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
-
- $title100 = $this->makeTitleObject( 100 );
- $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
- */
- public function testIsAllowedModel() {
- $handler = new MainSlotRoleHandler( [] );
-
- // For the main handler, (nearly) all models are allowed
- $title = $this->makeTitleObject( NS_MAIN );
- $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
- $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
- */
- public function testSupportsArticleCount() {
- $handler = new MainSlotRoleHandler( [] );
-
- $this->assertTrue( $handler->supportsArticleCount() );
- }
-
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use InvalidArgumentException;
-use LogicException;
-use MediaWiki\Revision\IncompleteRevisionException;
-use MediaWiki\Revision\SlotRecord;
-use MediaWiki\Revision\SuppressedDataException;
-use MediaWikiTestCase;
-use WikitextContent;
-
-/**
- * @covers \MediaWiki\Revision\SlotRecord
- */
-class SlotRecordTest extends MediaWikiTestCase {
-
- private function makeRow( $data = [] ) {
- $data = $data + [
- 'slot_id' => 1234,
- 'slot_content_id' => 33,
- 'content_size' => '5',
- 'content_sha1' => 'someHash',
- 'content_address' => 'tt:456',
- 'model_name' => CONTENT_MODEL_WIKITEXT,
- 'format_name' => CONTENT_FORMAT_WIKITEXT,
- 'slot_revision_id' => '2',
- 'slot_origin' => '1',
- 'role_name' => 'myRole',
- ];
- return (object)$data;
- }
-
- public function testCompleteConstruction() {
- $row = $this->makeRow();
- $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
- $this->assertTrue( $record->hasAddress() );
- $this->assertTrue( $record->hasContentId() );
- $this->assertTrue( $record->hasRevision() );
- $this->assertTrue( $record->isInherited() );
- $this->assertSame( 'A', $record->getContent()->getText() );
- $this->assertSame( 5, $record->getSize() );
- $this->assertSame( 'someHash', $record->getSha1() );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
- $this->assertSame( 2, $record->getRevision() );
- $this->assertSame( 1, $record->getOrigin() );
- $this->assertSame( 'tt:456', $record->getAddress() );
- $this->assertSame( 33, $record->getContentId() );
- $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
- $this->assertSame( 'myRole', $record->getRole() );
- }
-
- public function testConstructionDeferred() {
- $row = $this->makeRow( [
- 'content_size' => null, // to be computed
- 'content_sha1' => null, // to be computed
- 'format_name' => function () {
- return CONTENT_FORMAT_WIKITEXT;
- },
- 'slot_revision_id' => '2',
- 'slot_origin' => '2',
- 'slot_content_id' => function () {
- return null;
- },
- ] );
-
- $content = function () {
- return new WikitextContent( 'A' );
- };
-
- $record = new SlotRecord( $row, $content );
-
- $this->assertTrue( $record->hasAddress() );
- $this->assertTrue( $record->hasRevision() );
- $this->assertFalse( $record->hasContentId() );
- $this->assertFalse( $record->isInherited() );
- $this->assertSame( 'A', $record->getContent()->getText() );
- $this->assertSame( 1, $record->getSize() );
- $this->assertNotEmpty( $record->getSha1() );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
- $this->assertSame( 2, $record->getRevision() );
- $this->assertSame( 2, $record->getRevision() );
- $this->assertSame( 'tt:456', $record->getAddress() );
- $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
- $this->assertSame( 'myRole', $record->getRole() );
- }
-
- public function testNewUnsaved() {
- $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
-
- $this->assertFalse( $record->hasAddress() );
- $this->assertFalse( $record->hasContentId() );
- $this->assertFalse( $record->hasRevision() );
- $this->assertFalse( $record->isInherited() );
- $this->assertFalse( $record->hasOrigin() );
- $this->assertSame( 'A', $record->getContent()->getText() );
- $this->assertSame( 1, $record->getSize() );
- $this->assertNotEmpty( $record->getSha1() );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
- $this->assertSame( 'myRole', $record->getRole() );
- }
-
- public function provideInvalidConstruction() {
- yield 'both null' => [ null, null ];
- yield 'null row' => [ null, new WikitextContent( 'A' ) ];
- yield 'array row' => [ [], new WikitextContent( 'A' ) ];
- yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
- yield 'null content' => [ (object)[], null ];
- }
-
- /**
- * @dataProvider provideInvalidConstruction
- */
- public function testInvalidConstruction( $row, $content ) {
- $this->setExpectedException( InvalidArgumentException::class );
- new SlotRecord( $row, $content );
- }
-
- public function testGetContentId_fails() {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getContentId();
- }
-
- public function testGetAddress_fails() {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getAddress();
- }
-
- public function provideIncomplete() {
- $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- yield 'unsaved' => [ $unsaved ];
-
- $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
- $inherited = SlotRecord::newInherited( $parent );
- yield 'inherited' => [ $inherited ];
- }
-
- /**
- * @dataProvider provideIncomplete
- */
- public function testGetRevision_fails( SlotRecord $record ) {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getRevision();
- }
-
- /**
- * @dataProvider provideIncomplete
- */
- public function testGetOrigin_fails( SlotRecord $record ) {
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
- $this->setExpectedException( IncompleteRevisionException::class );
-
- $record->getOrigin();
- }
-
- public function provideHashStability() {
- yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
- yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
- }
-
- /**
- * @dataProvider provideHashStability
- */
- public function testHashStability( $text, $hash ) {
- // Changing the output of the hash function will break things horribly!
-
- $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
-
- $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
- $this->assertSame( $hash, $record->getSha1() );
- }
-
- public function testHashComputed() {
- $row = $this->makeRow();
- $row->content_sha1 = '';
-
- $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
- $this->assertNotEmpty( $rec->getSha1() );
- }
-
- public function testNewWithSuppressedContent() {
- $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
- $output = SlotRecord::newWithSuppressedContent( $input );
-
- $this->setExpectedException( SuppressedDataException::class );
- $output->getContent();
- }
-
- public function testNewInherited() {
- $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
- $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
- // This would happen while doing an edit, before saving revision meta-data.
- $inherited = SlotRecord::newInherited( $parent );
-
- $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
- $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
- $this->assertSame( $parent->getContent(), $inherited->getContent() );
- $this->assertTrue( $inherited->isInherited() );
- $this->assertTrue( $inherited->hasOrigin() );
- $this->assertFalse( $inherited->hasRevision() );
-
- // make sure we didn't mess with the internal state of $parent
- $this->assertFalse( $parent->isInherited() );
- $this->assertSame( 7, $parent->getRevision() );
-
- // This would happen while doing an edit, after saving the revision meta-data
- // and content meta-data.
- $saved = SlotRecord::newSaved(
- 10,
- $inherited->getContentId(),
- $inherited->getAddress(),
- $inherited
- );
- $this->assertSame( $parent->getContentId(), $saved->getContentId() );
- $this->assertSame( $parent->getAddress(), $saved->getAddress() );
- $this->assertSame( $parent->getContent(), $saved->getContent() );
- $this->assertTrue( $saved->isInherited() );
- $this->assertTrue( $saved->hasRevision() );
- $this->assertSame( 10, $saved->getRevision() );
-
- // make sure we didn't mess with the internal state of $parent or $inherited
- $this->assertSame( 7, $parent->getRevision() );
- $this->assertFalse( $inherited->hasRevision() );
- }
-
- public function testNewSaved() {
- // This would happen while doing an edit, before saving revision meta-data.
- $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
- // This would happen while doing an edit, after saving the revision meta-data
- // and content meta-data.
- $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
- $this->assertFalse( $saved->isInherited() );
- $this->assertTrue( $saved->hasOrigin() );
- $this->assertTrue( $saved->hasRevision() );
- $this->assertTrue( $saved->hasAddress() );
- $this->assertTrue( $saved->hasContentId() );
- $this->assertSame( 'theNewAddress', $saved->getAddress() );
- $this->assertSame( 20, $saved->getContentId() );
- $this->assertSame( 'A', $saved->getContent()->getText() );
- $this->assertSame( 10, $saved->getRevision() );
- $this->assertSame( 10, $saved->getOrigin() );
-
- // make sure we didn't mess with the internal state of $unsaved
- $this->assertFalse( $unsaved->hasAddress() );
- $this->assertFalse( $unsaved->hasContentId() );
- $this->assertFalse( $unsaved->hasRevision() );
- }
-
- public function provideNewSaved_LogicException() {
- $freshRow = $this->makeRow( [
- 'content_id' => 10,
- 'content_address' => 'address:1',
- 'slot_origin' => 1,
- 'slot_revision_id' => 1,
- ] );
-
- $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
- yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
- yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
- yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
-
- $inheritedRow = $this->makeRow( [
- 'content_id' => null,
- 'content_address' => null,
- 'slot_origin' => 0,
- 'slot_revision_id' => 1,
- ] );
-
- $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
- yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
- }
-
- /**
- * @dataProvider provideNewSaved_LogicException
- */
- public function testNewSaved_LogicException(
- $revisionId,
- $contentId,
- $contentAddress,
- SlotRecord $protoSlot
- ) {
- $this->setExpectedException( LogicException::class );
- SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
- }
-
- public function provideNewSaved_InvalidArgumentException() {
- $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
- yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
- yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
- yield 'bad content address' => [ 7, 5, 77, $unsaved ];
- }
-
- /**
- * @dataProvider provideNewSaved_InvalidArgumentException
- */
- public function testNewSaved_InvalidArgumentException(
- $revisionId,
- $contentId,
- $contentAddress,
- SlotRecord $protoSlot
- ) {
- $this->setExpectedException( InvalidArgumentException::class );
- SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
- }
-
- public function provideHasSameContent() {
- $fail = function () {
- self::fail( 'There should be no need to actually load the content.' );
- };
-
- $a100a1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a1',
- ]
- ),
- $fail
- );
- $a100a1b = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a1',
- ]
- ),
- $fail
- );
- $a100null = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => null,
- ]
- ),
- $fail
- );
- $a100a2 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a2',
- ]
- ),
- $fail
- );
- $b100a1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'B',
- 'content_size' => 100,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a1',
- ]
- ),
- $fail
- );
- $a200a1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 200,
- 'content_sha1' => 'hash-a',
- 'content_address' => 'xxx:a2',
- ]
- ),
- $fail
- );
- $a100x1 = new SlotRecord(
- $this->makeRow(
- [
- 'model_name' => 'A',
- 'content_size' => 100,
- 'content_sha1' => 'hash-x',
- 'content_address' => 'xxx:x1',
- ]
- ),
- $fail
- );
-
- yield 'same instance' => [ $a100a1, $a100a1, true ];
- yield 'no address' => [ $a100a1, $a100null, true ];
- yield 'same address' => [ $a100a1, $a100a1b, true ];
- yield 'different address' => [ $a100a1, $a100a2, true ];
- yield 'different model' => [ $a100a1, $b100a1, false ];
- yield 'different size' => [ $a100a1, $a200a1, false ];
- yield 'different hash' => [ $a100a1, $a100x1, false ];
- }
-
- /**
- * @dataProvider provideHasSameContent
- */
- public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
- $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
- $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @author Addshore
- * @covers TitleArrayFromResult
- */
-class TitleArrayFromResultTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- private function getMockResultWrapper( $row = null, $numRows = 1 ) {
- $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
- ->disableOriginalConstructor();
-
- $resultWrapper = $resultWrapper->getMock();
- $resultWrapper->expects( $this->atLeastOnce() )
- ->method( 'current' )
- ->will( $this->returnValue( $row ) );
- $resultWrapper->expects( $this->any() )
- ->method( 'numRows' )
- ->will( $this->returnValue( $numRows ) );
-
- return $resultWrapper;
- }
-
- private function getRowWithTitle( $namespace = 3, $title = 'foo' ) {
- $row = new stdClass();
- $row->page_namespace = $namespace;
- $row->page_title = $title;
- return $row;
- }
-
- /**
- * @covers TitleArrayFromResult::__construct
- */
- public function testConstructionWithFalseRow() {
- $row = false;
- $resultWrapper = $this->getMockResultWrapper( $row );
-
- $object = new TitleArrayFromResult( $resultWrapper );
-
- $this->assertEquals( $resultWrapper, $object->res );
- $this->assertSame( 0, $object->key );
- $this->assertEquals( $row, $object->current );
- }
-
- /**
- * @covers TitleArrayFromResult::__construct
- */
- public function testConstructionWithRow() {
- $namespace = 0;
- $title = 'foo';
- $row = $this->getRowWithTitle( $namespace, $title );
- $resultWrapper = $this->getMockResultWrapper( $row );
-
- $object = new TitleArrayFromResult( $resultWrapper );
-
- $this->assertEquals( $resultWrapper, $object->res );
- $this->assertSame( 0, $object->key );
- $this->assertInstanceOf( Title::class, $object->current );
- $this->assertEquals( $namespace, $object->current->mNamespace );
- $this->assertEquals( $title, $object->current->mTextform );
- }
-
- public static function provideNumberOfRows() {
- return [
- [ 0 ],
- [ 1 ],
- [ 122 ],
- ];
- }
-
- /**
- * @dataProvider provideNumberOfRows
- * @covers TitleArrayFromResult::count
- */
- public function testCountWithVaryingValues( $numRows ) {
- $object = new TitleArrayFromResult( $this->getMockResultWrapper(
- $this->getRowWithTitle(),
- $numRows
- ) );
- $this->assertEquals( $numRows, $object->count() );
- }
-
- /**
- * @covers TitleArrayFromResult::current
- */
- public function testCurrentAfterConstruction() {
- $namespace = 0;
- $title = 'foo';
- $row = $this->getRowWithTitle( $namespace, $title );
- $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
- $this->assertInstanceOf( Title::class, $object->current() );
- $this->assertEquals( $namespace, $object->current->mNamespace );
- $this->assertEquals( $title, $object->current->mTextform );
- }
-
- public function provideTestValid() {
- return [
- [ $this->getRowWithTitle(), true ],
- [ false, false ],
- ];
- }
-
- /**
- * @dataProvider provideTestValid
- * @covers TitleArrayFromResult::valid
- */
- public function testValid( $input, $expected ) {
- $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
- $this->assertEquals( $expected, $object->valid() );
- }
-
- // @todo unit test for key()
- // @todo unit test for next()
- // @todo unit test for rewind()
-}
+++ /dev/null
-<?php
-
-/**
- * @covers WikiReference
- */
-class WikiReferenceTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- public function provideGetDisplayName() {
- return [
- 'http' => [ 'foo.bar', 'http://foo.bar' ],
- 'https' => [ 'foo.bar', 'http://foo.bar' ],
-
- // apparently, this is the expected behavior
- 'invalid' => [ 'purple kittens', 'purple kittens' ],
- ];
- }
-
- /**
- * @dataProvider provideGetDisplayName
- */
- public function testGetDisplayName( $expected, $canonicalServer ) {
- $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
- $this->assertEquals( $expected, $reference->getDisplayName() );
- }
-
- public function testGetCanonicalServer() {
- $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
- $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
- }
-
- public function provideGetCanonicalUrl() {
- return [
- 'no fragment' => [
- 'https://acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- null
- ],
- 'empty fragment' => [
- 'https://acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- ''
- ],
- 'fragment' => [
- 'https://acme.com/wiki/Foo#Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar'
- ],
- 'double fragment' => [
- 'https://acme.com/wiki/Foo#Bar%23Xus',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar#Xus'
- ],
- 'escaped fragment' => [
- 'https://acme.com/wiki/Foo%23Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo#Bar',
- null
- ],
- 'empty path' => [
- 'https://acme.com/Foo',
- 'https://acme.com',
- '//acme.com',
- '/$1',
- 'Foo',
- null
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetCanonicalUrl
- */
- public function testGetCanonicalUrl(
- $expected, $canonicalServer, $server, $path, $page, $fragmentId
- ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
- }
-
- /**
- * @dataProvider provideGetCanonicalUrl
- * @note getUrl is an alias for getCanonicalUrl
- */
- public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
- }
-
- public function provideGetFullUrl() {
- return [
- 'no fragment' => [
- '//acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- null
- ],
- 'empty fragment' => [
- '//acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- ''
- ],
- 'fragment' => [
- '//acme.com/wiki/Foo#Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar'
- ],
- 'double fragment' => [
- '//acme.com/wiki/Foo#Bar%23Xus',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar#Xus'
- ],
- 'escaped fragment' => [
- '//acme.com/wiki/Foo%23Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo#Bar',
- null
- ],
- 'empty path' => [
- '//acme.com/Foo',
- 'https://acme.com',
- '//acme.com',
- '/$1',
- 'Foo',
- null
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetFullUrl
- */
- public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
- }
-
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Logger\Monolog;
-
-/**
- * Flay per https://phabricator.wikimedia.org/T218688.
- *
- * @group Broken
- * @covers \MediaWiki\Logger\Monolog\CeeFormatter
- */
-class CeeFormatterTest extends \PHPUnit\Framework\TestCase {
- public function testV1() {
- $ls_formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
- $cee_formatter = new CeeFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
- $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
- $this->assertSame(
- $cee_formatter->format( $record ),
- "@cee: " . $ls_formatter->format( $record ) );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @covers DifferenceEngineSlotDiffRenderer
- */
-class DifferenceEngineSlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
-
- public function testGetDiff() {
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
- $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
-
- $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
- $this->assertEquals( 'xxx|yyy', $diff );
-
- $diff = $slotDiffRenderer->getDiff( null, $newContent );
- $this->assertEquals( '|yyy', $diff );
-
- $diff = $slotDiffRenderer->getDiff( $oldContent, null );
- $this->assertEquals( 'xxx|', $diff );
- }
-
- public function testAddModules() {
- $output = $this->getMockBuilder( OutputPage::class )
- ->disableOriginalConstructor()
- ->setMethods( [ 'addModules' ] )
- ->getMock();
- $output->expects( $this->once() )
- ->method( 'addModules' )
- ->with( 'foo' );
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $slotDiffRenderer->addModules( $output );
- }
-
- public function testGetExtraCacheKeys() {
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
- $this->assertSame( [ 'foo' ], $extraCacheKeys );
- }
-
-}
+++ /dev/null
-<?php
-
-use Wikimedia\Assert\ParameterTypeException;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers SlotDiffRenderer
- */
-class SlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
-
- /**
- * @dataProvider provideNormalizeContents
- */
- public function testNormalizeContents(
- $oldContent, $newContent, $allowedClasses,
- $expectedOldContent, $expectedNewContent, $expectedExceptionClass
- ) {
- $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
- ->getMock();
- try {
- // __call needs help deciding which parameter to take by reference
- call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
- 'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
- $this->assertEquals( $expectedOldContent, $oldContent );
- $this->assertEquals( $expectedNewContent, $newContent );
- } catch ( Exception $e ) {
- if ( !$expectedExceptionClass ) {
- throw $e;
- }
- $this->assertInstanceOf( $expectedExceptionClass, $e );
- }
- }
-
- public function provideNormalizeContents() {
- return [
- 'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
- 'left null' => [
- null, new WikitextContent( 'abc' ), null,
- new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
- ],
- 'right null' => [
- new WikitextContent( 'def' ), null, null,
- new WikitextContent( 'def' ), new WikitextContent( '' ), null,
- ],
- 'type filter' => [
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
- ],
- 'type filter (subclass)' => [
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
- ],
- 'type filter (null)' => [
- new WikitextContent( 'abc' ), null, TextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
- ],
- 'type filter failure (left)' => [
- new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
- null, null, ParameterTypeException::class,
- ],
- 'type filter failure (right)' => [
- new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
- null, null, ParameterTypeException::class,
- ],
- 'type filter (array syntax)' => [
- new WikitextContent( 'abc' ), new JsonContent( 'def' ),
- [ JsonContent::class, WikitextContent::class ],
- new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
- ],
- 'type filter failure (array syntax)' => [
- new WikitextContent( 'abc' ), new CssContent( 'def' ),
- [ JsonContent::class, WikitextContent::class ],
- null, null, ParameterTypeException::class,
- ],
- ];
- }
-
-}
+++ /dev/null
-<?php
-
-class FileBackendDBRepoWrapperTest extends MediaWikiTestCase {
- protected $backendName = 'foo-backend';
- protected $repoName = 'pureTestRepo';
-
- /**
- * @dataProvider getBackendPathsProvider
- * @covers FileBackendDBRepoWrapper::getBackendPaths
- */
- public function testGetBackendPaths(
- $mocks,
- $latest,
- $dbReadsExpected,
- $dbReturnValue,
- $originalPath,
- $expectedBackendPath,
- $message ) {
- list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
-
- $dbMock->expects( $dbReadsExpected )
- ->method( 'selectField' )
- ->will( $this->returnValue( $dbReturnValue ) );
-
- $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
-
- $this->assertEquals(
- $expectedBackendPath,
- $newPaths[0],
- $message );
- }
-
- public function getBackendPathsProvider() {
- $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
- $mocksForCaching = $this->getMocks();
-
- return [
- [
- $mocksForCaching,
- false,
- $this->once(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'Public path translated correctly',
- ],
- [
- $mocksForCaching,
- false,
- $this->never(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'LRU cache leveraged',
- ],
- [
- $this->getMocks(),
- true,
- $this->once(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'Latest obtained',
- ],
- [
- $this->getMocks(),
- true,
- $this->never(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-deleted/f/o/foobar.jpg',
- $prefix . '-original/f/o/o/foobar',
- 'Deleted path translated correctly',
- ],
- [
- $this->getMocks(),
- true,
- $this->once(),
- null,
- $prefix . '-public/b/a/baz.jpg',
- $prefix . '-public/b/a/baz.jpg',
- 'Path left untouched if no sha1 can be found',
- ],
- ];
- }
-
- /**
- * @covers FileBackendDBRepoWrapper::getFileContentsMulti
- */
- public function testGetFileContentsMulti() {
- list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
-
- $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
- . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
- $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
- . '-public/f/o/foobar.jpg';
-
- $dbMock->expects( $this->once() )
- ->method( 'selectField' )
- ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
-
- $backendMock->expects( $this->once() )
- ->method( 'getFileContentsMulti' )
- ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
-
- $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
-
- $this->assertEquals(
- [ $filenamePath => 'foo' ],
- $result,
- 'File contents paths translated properly'
- );
- }
-
- protected function getMocks() {
- $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
- ->disableOriginalClone()
- ->disableOriginalConstructor()
- ->getMock();
-
- $backendMock = $this->getMockBuilder( FSFileBackend::class )
- ->setConstructorArgs( [ [
- 'name' => $this->backendName,
- 'wikiId' => wfWikiID()
- ] ] )
- ->getMock();
-
- $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
- ->setMethods( [ 'getDB' ] )
- ->setConstructorArgs( [ [
- 'backend' => $backendMock,
- 'repoName' => $this->repoName,
- 'dbHandleFactory' => null
- ] ] )
- ->getMock();
-
- $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
-
- return [ $dbMock, $backendMock, $wrapperMock ];
- }
-}
+++ /dev/null
-<?php
-
-/** @covers ForeignDBFile */
-class ForeignDBFileTest extends \PHPUnit\Framework\TestCase {
-
- use PHPUnit4And6Compat;
-
- public function testShouldConstructCorrectInstanceFromTitle() {
- $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
- $repoMock = $this->createMock( LocalRepo::class );
-
- $file = ForeignDBFile::newFromTitle( $title, $repoMock );
-
- $this->assertInstanceOf( ForeignDBFile::class, $file );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @covers HTMLCheckMatrix
- */
-class HTMLCheckMatrixTest extends MediaWikiTestCase {
- private static $defaultOptions = [
- 'rows' => [ 'r1', 'r2' ],
- 'columns' => [ 'c1', 'c2' ],
- 'fieldname' => 'test',
- ];
-
- public function testPlainInstantiation() {
- try {
- new HTMLCheckMatrix( [] );
- } catch ( MWException $e ) {
- $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
- return;
- }
-
- $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
- }
-
- public function testInstantiationWithMinimumRequiredParameters() {
- new HTMLCheckMatrix( self::$defaultOptions );
- $this->assertTrue( true ); // form instantiation must throw exception on failure
- }
-
- public function testValidateCallsUserDefinedValidationCallback() {
- $called = false;
- $field = new HTMLCheckMatrix( self::$defaultOptions + [
- 'validation-callback' => function () use ( &$called ) {
- $called = true;
-
- return false;
- },
- ] );
- $this->assertEquals( false, $this->validate( $field, [] ) );
- $this->assertTrue( $called );
- }
-
- public function testValidateRequiresArrayInput() {
- $field = new HTMLCheckMatrix( self::$defaultOptions );
- $this->assertEquals( false, $this->validate( $field, null ) );
- $this->assertEquals( false, $this->validate( $field, true ) );
- $this->assertEquals( false, $this->validate( $field, 'abc' ) );
- $this->assertEquals( false, $this->validate( $field, new stdClass ) );
- $this->assertEquals( true, $this->validate( $field, [] ) );
- }
-
- public function testValidateAllowsOnlyKnownTags() {
- $field = new HTMLCheckMatrix( self::$defaultOptions );
- $this->assertInstanceOf( Message::class, $this->validate( $field, [ 'foo' ] ) );
- }
-
- public function testValidateAcceptsPartialTagList() {
- $field = new HTMLCheckMatrix( self::$defaultOptions );
- $this->assertTrue( $this->validate( $field, [] ) );
- $this->assertTrue( $this->validate( $field, [ 'c1-r1' ] ) );
- $this->assertTrue( $this->validate( $field, [ 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ] ) );
- }
-
- /**
- * This form object actually has no visibility into what happens later on, but essentially
- * if the data submitted by the user passes validate the following is run:
- * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
- * $user->setOption( $k, $v );
- * }
- */
- public function testValuesForcedOnRemainOn() {
- $field = new HTMLCheckMatrix( self::$defaultOptions + [
- 'force-options-on' => [ 'c2-r1' ],
- ] );
- $expected = [
- 'c1-r1' => false,
- 'c1-r2' => false,
- 'c2-r1' => true,
- 'c2-r2' => false,
- ];
- $this->assertEquals( $expected, $field->filterDataForSubmit( [] ) );
- }
-
- public function testValuesForcedOffRemainOff() {
- $field = new HTMLCheckMatrix( self::$defaultOptions + [
- 'force-options-off' => [ 'c1-r2', 'c2-r2' ],
- ] );
- $expected = [
- 'c1-r1' => true,
- 'c1-r2' => false,
- 'c2-r1' => true,
- 'c2-r2' => false,
- ];
- // array_keys on the result simulates submitting all fields checked
- $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
- }
-
- protected function validate( HTMLFormField $field, $submitted ) {
- return $field->validate(
- $submitted,
- [ self::$defaultOptions['fieldname'] => $submitted ]
- );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @covers FormatJson
- */
-class FormatJsonTest extends MediaWikiTestCase {
-
- /**
- * Test data for testParseTryFixing.
- *
- * Some PHP interpreters use json-c rather than the JSON.org canonical
- * parser to avoid being encumbered by the "shall be used for Good, not
- * Evil" clause of the JSON.org parser's license. By default, json-c
- * parses in a non-strict mode which allows trailing commas for array and
- * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
- * block is not always triggered. It however isn't lenient in exactly the
- * same ways as our TRY_FIXING mode, so the assertions in this test are
- * a bit more complicated than they ideally would be:
- *
- * Optional third argument: true if json-c parses the value without
- * intervention, false otherwise. Defaults to true.
- *
- * Optional fourth argument: expected cannonical JSON serialization of
- * json-c parsed result. Defaults to the second argument's value.
- */
- public static function provideParseTryFixing() {
- return [
- [ "[,]", '[]', false ],
- [ "[ , ]", '[]', false ],
- [ "[ , }", false ],
- [ '[1],', false, true, '[1]' ],
- [ "[1,]", '[1]' ],
- [ "[1\n,]", '[1]' ],
- [ "[1,\n]", '[1]' ],
- [ "[1,]\n", '[1]' ],
- [ "[1\n,\n]\n", '[1]' ],
- [ '["a,",]', '["a,"]' ],
- [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
- // I wish we could parse this, but would need quote parsing
- [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
- [ '[1,,]', false, false, '[1]' ],
- ];
- }
-
- /**
- * @dataProvider provideParseTryFixing
- * @param string $value
- * @param string|bool $expected Expected result with strict parser
- * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
- * @param string|bool $expectedJsonc Expected result with lenient parser
- * if different from the strict expectation
- */
- public function testParseTryFixing(
- $value, $expected,
- $jsoncParses = true, $expectedJsonc = null
- ) {
- // PHP5 results are always expected to have isGood() === false
- $expectedGoodStatus = false;
-
- // Check to see if json parser allows trailing commas
- if ( json_decode( '[1,]' ) !== null ) {
- // Use json-c specific expected result if provided
- $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
- // If json-c parses the value natively, expect isGood() === true
- $expectedGoodStatus = $jsoncParses;
- }
-
- $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
- $this->assertInstanceOf( Status::class, $st );
- if ( $expected === false ) {
- $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
- } else {
- $this->assertSame( $expectedGoodStatus, $st->isGood(),
- 'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
- );
- $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
- $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
- $this->assertEquals( $expected, $val );
- }
- }
-
-}
+++ /dev/null
-<?php
-/**
- * @todo Could use a test of extended XMP segments. Hard to find programs that
- * create example files, and creating my own in vim propbably wouldn't
- * serve as a very good "test". (Adobe photoshop probably creates such files
- * but it costs money). The implementation of it currently in MediaWiki is based
- * solely on reading the standard, without any real world test files.
- *
- * @group Media
- * @covers JpegMetadataExtractor
- */
-class JpegMetadataExtractorTest extends MediaWikiTestCase {
-
- protected $filePath;
-
- protected function setUp() {
- parent::setUp();
-
- $this->filePath = __DIR__ . '/../../data/media/';
- }
-
- /**
- * We also use this test to test padding bytes don't
- * screw stuff up
- *
- * @param string $file Filename
- *
- * @dataProvider provideUtf8Comment
- */
- public function testUtf8Comment( $file ) {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
- $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
- }
-
- public static function provideUtf8Comment() {
- return [
- [ 'jpeg-comment-utf.jpg' ],
- [ 'jpeg-padding-even.jpg' ],
- [ 'jpeg-padding-odd.jpg' ],
- ];
- }
-
- /** The file is iso-8859-1, but it should get auto converted */
- public function testIso88591Comment() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
- $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
- }
-
- /** Comment values that are non-textual (random binary junk) should not be shown.
- * The example test file has a comment with a 0x5 byte in it which is a control character
- * and considered binary junk for our purposes.
- */
- public function testBinaryCommentStripped() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
- $this->assertEmpty( $res['COM'] );
- }
-
- /* Very rarely a file can have multiple comments.
- * Order of comments is based on order inside the file.
- */
- public function testMultipleComment() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
- $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
- }
-
- public function testXMPExtraction() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
- $this->assertEquals( $expected, $res['XMP'] );
- }
-
- public function testPSIRExtraction() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $expected = '50686f746f73686f7020332e30003842494d04040000000'
- . '000181c02190004746573741c02190003666f6f1c020000020004';
- $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
- }
-
- public function testXMPExtractionAltAppId() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
- $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
- $this->assertEquals( $expected, $res['XMP'] );
- }
-
- public function testIPTCHashComparisionNoHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-no-hash', $res );
- }
-
- public function testIPTCHashComparisionBadHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-bad-hash', $res );
- }
-
- public function testIPTCHashComparisionGoodHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-good-hash', $res );
- }
-
- public function testExifByteOrder() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
- $expected = 'BE';
- $this->assertEquals( $expected, $res['byteOrder'] );
- }
-
- public function testInfiniteRead() {
- // test file truncated right after a segment, which previously
- // caused an infinite loop looking for the next segment byte.
- // Should get past infinite loop and throw in wfUnpack()
- $this->setExpectedException( 'MWException' );
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
- }
-
- public function testInfiniteRead2() {
- // test file truncated after a segment's marker and size, which
- // would cause a seek past end of file. Seek past end of file
- // doesn't actually fail, but prevents further reading and was
- // devolving into the previous case (testInfiniteRead).
- $this->setExpectedException( 'MWException' );
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
- }
-}
+++ /dev/null
-<?php
-
-class ArticleTest extends MediaWikiTestCase {
-
- /**
- * @var Title
- */
- private $title;
- /**
- * @var Article
- */
- private $article;
-
- /** creates a title object and its article object */
- protected function setUp() {
- parent::setUp();
- $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
- $this->article = new Article( $this->title );
- }
-
- /** cleanup title object and its article object */
- protected function tearDown() {
- parent::tearDown();
- $this->title = null;
- $this->article = null;
- }
-
- /**
- * @covers Article::__get
- */
- public function testImplementsGetMagic() {
- $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
- }
-
- /**
- * @depends testImplementsGetMagic
- * @covers Article::__set
- */
- public function testImplementsSetMagic() {
- $this->article->mLatest = 2;
- $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
- }
-
- /**
- * @covers Article::__get
- * @covers Article::__set
- */
- public function testGetOrSetOnNewProperty() {
- $this->article->ext_someNewProperty = 12;
- $this->assertEquals( 12, $this->article->ext_someNewProperty,
- "Article get/set magic on new field" );
-
- $this->article->ext_someNewProperty = -8;
- $this->assertEquals( -8, $this->article->ext_someNewProperty,
- "Article get/set magic on update to new field" );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\Token
- */
-class TokenTest extends MediaWikiTestCase {
-
- public function testBasics() {
- $token = $this->getMockBuilder( Token::class )
- ->setMethods( [ 'toStringAtTimestamp' ] )
- ->setConstructorArgs( [ 'sekret', 'salty', true ] )
- ->getMock();
- $token->expects( $this->any() )->method( 'toStringAtTimestamp' )
- ->will( $this->returnValue( 'faketoken+\\' ) );
-
- $this->assertSame( 'faketoken+\\', $token->toString() );
- $this->assertSame( 'faketoken+\\', (string)$token );
- $this->assertTrue( $token->wasNew() );
-
- $token = new Token( 'sekret', 'salty', false );
- $this->assertFalse( $token->wasNew() );
- }
-
- public function testToStringAtTimestamp() {
- $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
-
- $this->assertSame(
- 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\',
- $token->toStringAtTimestamp( 1447362018 )
- );
- $this->assertSame(
- 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\',
- $token->toStringAtTimestamp( 1447362026 )
- );
- }
-
- public function testGetTimestamp() {
- $this->assertSame(
- 1447362018, Token::getTimestamp( 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\' )
- );
- $this->assertSame(
- 1447362026, Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\' )
- );
- $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
- $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be+\\' ) );
-
- $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9x76c224cfb400d43be5644fdea+\\' ) );
- }
-
- public function testMatch() {
- $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
-
- $test = $token->toStringAtTimestamp( time() - 10 );
- $this->assertTrue( $token->match( $test ) );
- $this->assertTrue( $token->match( $test, 12 ) );
- $this->assertFalse( $token->match( $test, 8 ) );
-
- $this->assertFalse( $token->match( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
- *
- * 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.
- *
- */
-
-use MediaWiki\Shell\FirejailCommand;
-use MediaWiki\Shell\Shell;
-use Wikimedia\TestingAccessWrapper;
-
-class FirejailCommandTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- public function provideBuildFinalCommand() {
- global $IP;
- // phpcs:ignore Generic.Files.LineLength
- $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
- $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
- $profile = "--profile=$IP/includes/shell/firejail.profile";
- $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
- $default = "$blacklist --noroot --seccomp --private-dev";
- return [
- [
- 'No restrictions',
- 'ls', 0, "$limit ''\''ls'\''' $env"
- ],
- [
- 'default restriction',
- 'ls', Shell::RESTRICT_DEFAULT,
- "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
- ],
- [
- 'no network',
- 'ls', Shell::NO_NETWORK,
- "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
- ],
- [
- 'default restriction & no network',
- 'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
- "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
- ],
- [
- 'seccomp',
- 'ls', Shell::SECCOMP,
- "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
- ],
- [
- 'seccomp & no execve',
- 'ls', Shell::SECCOMP | Shell::NO_EXECVE,
- "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
- ],
- ];
- }
-
- /**
- * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
- * @dataProvider provideBuildFinalCommand
- */
- public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
- $command = new FirejailCommand( 'firejail' );
- $command
- ->params( $params )
- ->restrict( $flags );
- $wrapper = TestingAccessWrapper::newFromObject( $command );
- $output = $wrapper->buildFinalCommand( $wrapper->command );
- $this->assertEquals( $expected, $output[0], $desc );
- }
-
-}
+++ /dev/null
-<?php
-
-use MediaWiki\Site\MediaWikiPageNameNormalizer;
-
-/**
- * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
- *
- * 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
- *
- * @since 1.27
- *
- * @group Site
- * @group medium
- *
- * @author Marius Hoch
- */
-class MediaWikiPageNameNormalizerTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- /**
- * @dataProvider normalizePageTitleProvider
- */
- public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
- MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
-
- $normalizer = new MediaWikiPageNameNormalizer(
- new MediaWikiPageNameNormalizerTestMockHttp()
- );
-
- $this->assertSame(
- $expected,
- $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
- );
- }
-
- public function normalizePageTitleProvider() {
- // Response are taken from wikidata and kkwiki using the following API request
- // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
- return [
- 'universe (Q1)' => [
- 'Q1',
- 'Q1',
- '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
- . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
- . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
- . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
- ],
- 'Q404 redirects to Q395' => [
- 'Q395',
- 'Q404',
- '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
- . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
- . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
- . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
- ],
- 'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
- 'Д',
- 'D',
- '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
- . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
- . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
- . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
- . '"lastrevid":2373618,"length":3501}}}}'
- ],
- 'there is no Q0' => [
- false,
- 'Q0',
- '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
- . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
- . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
- ],
- 'invalid title' => [
- false,
- '{{',
- '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
- . '"invalidreason":"The requested page title contains invalid '
- . 'characters: \"{\".","invalid":""}}}}'
- ],
- 'error on get' => [ false, 'ABC', false ]
- ];
- }
-
-}
-
-/**
- * @private
- * @see Http
- */
-class MediaWikiPageNameNormalizerTestMockHttp extends Http {
-
- /**
- * @var mixed
- */
- public static $response;
-
- public static function get( $url, array $options = [], $caller = __METHOD__ ) {
- PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
- PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
-
- return self::$response;
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @covers ZipDirectoryReader
- * NOTE: this test is more like an integration test than a unit test
- */
-class ZipDirectoryReaderTest extends PHPUnit\Framework\TestCase {
-
- use MediaWikiCoversValidator;
-
- protected $zipDir;
- protected $entries;
-
- protected function setUp() {
- parent::setUp();
- $this->zipDir = __DIR__ . '/../../data/zip';
- }
-
- function zipCallback( $entry ) {
- $this->entries[] = $entry;
- }
-
- function readZipAssertError( $file, $error, $assertMessage ) {
- $this->entries = [];
- $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
- $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
- }
-
- function readZipAssertSuccess( $file, $assertMessage ) {
- $this->entries = [];
- $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
- $this->assertTrue( $status->isOK(), $assertMessage );
- }
-
- public function testEmpty() {
- $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
- }
-
- public function testMultiDisk0() {
- $this->readZipAssertError( 'split.zip', 'zip-unsupported',
- 'Split zip error' );
- }
-
- public function testNoSignature() {
- $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
- 'No signature should give "wrong format" error' );
- }
-
- public function testSimple() {
- $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
- $this->assertEquals( $this->entries, [ [
- 'name' => 'Class.class',
- 'mtime' => '20010115000000',
- 'size' => 1,
- ] ] );
- }
-
- public function testBadCentralEntrySignature() {
- $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
- 'Bad central entry error' );
- }
-
- public function testTrailingBytes() {
- // Due to T40432 this is now zip-wrong-format instead of zip-bad
- $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
- 'Trailing bytes error' );
- }
-
- public function testWrongCDStart() {
- $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
- 'Wrong CD start disk error' );
- }
-
- public function testCentralDirectoryGap() {
- $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
- 'CD gap error' );
- }
-
- public function testCentralDirectoryTruncated() {
- $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
- 'CD truncated error (should hit unpack() overrun)' );
- }
-
- public function testLooksLikeZip64() {
- $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
- 'A file which looks like ZIP64 but isn\'t, should give error' );
- }
-}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfAppendQuery
+ */
+class WfAppendQueryTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider provideAppendQuery
+ */
+ public function testAppendQuery( $url, $query, $expected, $message = null ) {
+ $this->assertEquals( $expected, wfAppendQuery( $url, $query ), $message );
+ }
+
+ public static function provideAppendQuery() {
+ return [
+ [
+ 'http://www.example.org/index.php',
+ '',
+ 'http://www.example.org/index.php',
+ 'No query'
+ ],
+ [
+ 'http://www.example.org/index.php',
+ [ 'foo' => 'bar' ],
+ 'http://www.example.org/index.php?foo=bar',
+ 'Set query array'
+ ],
+ [
+ 'http://www.example.org/index.php?foz=baz',
+ 'foo=bar',
+ 'http://www.example.org/index.php?foz=baz&foo=bar',
+ 'Set query string'
+ ],
+ [
+ 'http://www.example.org/index.php?foo=bar',
+ '',
+ 'http://www.example.org/index.php?foo=bar',
+ 'Empty string with query'
+ ],
+ [
+ 'http://www.example.org/index.php?foo=bar',
+ [ 'baz' => 'quux' ],
+ 'http://www.example.org/index.php?foo=bar&baz=quux',
+ 'Add query array'
+ ],
+ [
+ 'http://www.example.org/index.php?foo=bar',
+ 'baz=quux',
+ 'http://www.example.org/index.php?foo=bar&baz=quux',
+ 'Add query string'
+ ],
+ [
+ 'http://www.example.org/index.php?foo=bar',
+ [ 'baz' => 'quux', 'foo' => 'baz' ],
+ 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
+ 'Modify query array'
+ ],
+ [
+ 'http://www.example.org/index.php?foo=bar',
+ 'baz=quux&foo=baz',
+ 'http://www.example.org/index.php?foo=bar&baz=quux&foo=baz',
+ 'Modify query string'
+ ],
+ [
+ 'http://www.example.org/index.php#baz',
+ 'foo=bar',
+ 'http://www.example.org/index.php?foo=bar#baz',
+ 'URL with fragment'
+ ],
+ [
+ 'http://www.example.org/index.php?foo=bar#baz',
+ 'quux=blah',
+ 'http://www.example.org/index.php?foo=bar&quux=blah#baz',
+ 'URL with query string and fragment'
+ ]
+ ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfArrayPlus2d
+ */
+class WfArrayPlus2dTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider provideArrays
+ */
+ public function testWfArrayPlus2d( $baseArray, $newValues, $expected, $testName ) {
+ $this->assertEquals(
+ $expected,
+ wfArrayPlus2d( $baseArray, $newValues ),
+ $testName
+ );
+ }
+
+ /**
+ * Provider for testing wfArrayPlus2d
+ *
+ * @return array
+ */
+ public static function provideArrays() {
+ return [
+ // target array, new values array, expected result
+ [
+ [ 0 => '1dArray' ],
+ [ 1 => '1dArray' ],
+ [ 0 => '1dArray', 1 => '1dArray' ],
+ "Test simple union of two arrays with different keys",
+ ],
+ [
+ [
+ 0 => [ 0 => '2dArray' ],
+ ],
+ [
+ 0 => [ 1 => '2dArray' ],
+ ],
+ [
+ 0 => [ 0 => '2dArray', 1 => '2dArray' ],
+ ],
+ "Test union of 2d arrays with different keys in the value array",
+ ],
+ [
+ [
+ 0 => [ 0 => '2dArray' ],
+ ],
+ [
+ 0 => [ 0 => '1dArray' ],
+ ],
+ [
+ 0 => [ 0 => '2dArray' ],
+ ],
+ "Test union of 2d arrays with same keys in the value array",
+ ],
+ [
+ [
+ 0 => [ 0 => [ 0 => '3dArray' ] ],
+ ],
+ [
+ 0 => [ 0 => [ 1 => '2dArray' ] ],
+ ],
+ [
+ 0 => [ 0 => [ 0 => '3dArray' ] ],
+ ],
+ "Test union of 3d array with different keys",
+ ],
+ [
+ [
+ 0 => [ 0 => [ 0 => '3dArray' ] ],
+ ],
+ [
+ 0 => [ 1 => [ 0 => '2dArray' ] ],
+ ],
+ [
+ 0 => [ 0 => [ 0 => '3dArray' ], 1 => [ 0 => '2dArray' ] ],
+ ],
+ "Test union of 3d array with different keys in the value array",
+ ],
+ [
+ [
+ 0 => [ 0 => [ 0 => '3dArray' ] ],
+ ],
+ [
+ 0 => [ 0 => [ 0 => '2dArray' ] ],
+ ],
+ [
+ 0 => [ 0 => [ 0 => '3dArray' ] ],
+ ],
+ "Test union of 3d array with same keys in the value array",
+ ],
+ ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfAssembleUrl
+ */
+class WfAssembleUrlTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider provideURLParts
+ */
+ public function testWfAssembleUrl( $parts, $output ) {
+ $partsDump = print_r( $parts, true );
+ $this->assertEquals(
+ $output,
+ wfAssembleUrl( $parts ),
+ "Testing $partsDump assembles to $output"
+ );
+ }
+
+ /**
+ * Provider of URL parts for testing wfAssembleUrl()
+ *
+ * @return array
+ */
+ public static function provideURLParts() {
+ $schemes = [
+ '' => [],
+ '//' => [
+ 'delimiter' => '//',
+ ],
+ 'http://' => [
+ 'scheme' => 'http',
+ 'delimiter' => '://',
+ ],
+ ];
+
+ $hosts = [
+ '' => [],
+ 'example.com' => [
+ 'host' => 'example.com',
+ ],
+ 'example.com:123' => [
+ 'host' => 'example.com',
+ 'port' => 123,
+ ],
+ 'id@example.com' => [
+ 'user' => 'id',
+ 'host' => 'example.com',
+ ],
+ 'id@example.com:123' => [
+ 'user' => 'id',
+ 'host' => 'example.com',
+ 'port' => 123,
+ ],
+ 'id:key@example.com' => [
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.com',
+ ],
+ 'id:key@example.com:123' => [
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.com',
+ 'port' => 123,
+ ],
+ ];
+
+ $cases = [];
+ foreach ( $schemes as $scheme => $schemeParts ) {
+ foreach ( $hosts as $host => $hostParts ) {
+ foreach ( [ '', '/path' ] as $path ) {
+ foreach ( [ '', 'query' ] as $query ) {
+ foreach ( [ '', 'fragment' ] as $fragment ) {
+ $parts = array_merge(
+ $schemeParts,
+ $hostParts
+ );
+ $url = $scheme .
+ $host .
+ $path;
+
+ if ( $path ) {
+ $parts['path'] = $path;
+ }
+ if ( $query ) {
+ $parts['query'] = $query;
+ $url .= '?' . $query;
+ }
+ if ( $fragment ) {
+ $parts['fragment'] = $fragment;
+ $url .= '#' . $fragment;
+ }
+
+ $cases[] = [
+ $parts,
+ $url,
+ ];
+ }
+ }
+ }
+ }
+ }
+
+ $complexURL = 'http://id:key@example.org:321' .
+ '/over/there?name=ferret&foo=bar#nose';
+ $cases[] = [
+ wfParseUrl( $complexURL ),
+ $complexURL,
+ ];
+
+ return $cases;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @group GlobalFunctions
+ * @covers ::wfBaseName
+ */
+class WfBaseNameTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider providePaths
+ */
+ public function testBaseName( $fullpath, $basename ) {
+ $this->assertEquals( $basename, wfBaseName( $fullpath ),
+ "wfBaseName('$fullpath') => '$basename'" );
+ }
+
+ public static function providePaths() {
+ return [
+ [ '', '' ],
+ [ '/', '' ],
+ [ '\\', '' ],
+ [ '//', '' ],
+ [ '\\\\', '' ],
+ [ 'a', 'a' ],
+ [ 'aaaa', 'aaaa' ],
+ [ '/a', 'a' ],
+ [ '\\a', 'a' ],
+ [ '/aaaa', 'aaaa' ],
+ [ '\\aaaa', 'aaaa' ],
+ [ '/aaaa/', 'aaaa' ],
+ [ '\\aaaa\\', 'aaaa' ],
+ [ '\\aaaa\\', 'aaaa' ],
+ [
+ '/mnt/upload3/wikipedia/en/thumb/8/8b/'
+ . 'Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
+ '93px-Zork_Grand_Inquisitor_box_cover.jpg'
+ ],
+ [ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ],
+ [ 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ],
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfEscapeShellArg
+ */
+class WfEscapeShellArgTest extends MediaWikiUnitTestCase {
+ public function testSingleInput() {
+ if ( wfIsWindows() ) {
+ $expected = '"blah"';
+ } else {
+ $expected = "'blah'";
+ }
+
+ $actual = wfEscapeShellArg( 'blah' );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public function testMultipleArgs() {
+ if ( wfIsWindows() ) {
+ $expected = '"foo" "bar" "baz"';
+ } else {
+ $expected = "'foo' 'bar' 'baz'";
+ }
+
+ $actual = wfEscapeShellArg( 'foo', 'bar', 'baz' );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public function testMultipleArgsAsArray() {
+ if ( wfIsWindows() ) {
+ $expected = '"foo" "bar" "baz"';
+ } else {
+ $expected = "'foo' 'bar' 'baz'";
+ }
+
+ $actual = wfEscapeShellArg( [ 'foo', 'bar', 'baz' ] );
+
+ $this->assertEquals( $expected, $actual );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfGetCaller
+ */
+class WfGetCallerTest extends MediaWikiUnitTestCase {
+ public function testZero() {
+ $this->assertEquals( 'WfGetCallerTest->testZero', wfGetCaller( 1 ) );
+ }
+
+ function callerOne() {
+ return wfGetCaller();
+ }
+
+ public function testOne() {
+ $this->assertEquals( 'WfGetCallerTest->testOne', self::callerOne() );
+ }
+
+ static function intermediateFunction( $level = 2, $n = 0 ) {
+ if ( $n > 0 ) {
+ return self::intermediateFunction( $level, $n - 1 );
+ }
+
+ return wfGetCaller( $level );
+ }
+
+ public function testTwo() {
+ $this->assertEquals( 'WfGetCallerTest->testTwo', self::intermediateFunction() );
+ }
+
+ public function testN() {
+ $this->assertEquals( 'WfGetCallerTest->testN', self::intermediateFunction( 2, 0 ) );
+ $this->assertEquals(
+ 'WfGetCallerTest::intermediateFunction',
+ self::intermediateFunction( 1, 0 )
+ );
+
+ for ( $i = 0; $i < 10; $i++ ) {
+ $this->assertEquals(
+ 'WfGetCallerTest::intermediateFunction',
+ self::intermediateFunction( $i + 1, $i )
+ );
+ }
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfRemoveDotSegments
+ */
+class WfRemoveDotSegmentsTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider providePaths
+ */
+ public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
+ $this->assertEquals(
+ $outputPath,
+ wfRemoveDotSegments( $inputPath ),
+ "Testing $inputPath expands to $outputPath"
+ );
+ }
+
+ /**
+ * Provider of URL paths for testing wfRemoveDotSegments()
+ *
+ * @return array
+ */
+ public static function providePaths() {
+ return [
+ [ '/a/b/c/./../../g', '/a/g' ],
+ [ 'mid/content=5/../6', 'mid/6' ],
+ [ '/a//../b', '/a/b' ],
+ [ '/.../a', '/.../a' ],
+ [ '.../a', '.../a' ],
+ [ '', '' ],
+ [ '/', '/' ],
+ [ '//', '//' ],
+ [ '.', '' ],
+ [ '..', '' ],
+ [ '...', '...' ],
+ [ '/.', '/' ],
+ [ '/..', '/' ],
+ [ './', '' ],
+ [ '../', '' ],
+ [ './a', 'a' ],
+ [ '../a', 'a' ],
+ [ '../../a', 'a' ],
+ [ '.././a', 'a' ],
+ [ './../a', 'a' ],
+ [ '././a', 'a' ],
+ [ '../../', '' ],
+ [ '.././', '' ],
+ [ './../', '' ],
+ [ '././', '' ],
+ [ '../..', '' ],
+ [ '../.', '' ],
+ [ './..', '' ],
+ [ './.', '' ],
+ [ '/../../a', '/a' ],
+ [ '/.././a', '/a' ],
+ [ '/./../a', '/a' ],
+ [ '/././a', '/a' ],
+ [ '/../../', '/' ],
+ [ '/.././', '/' ],
+ [ '/./../', '/' ],
+ [ '/././', '/' ],
+ [ '/../..', '/' ],
+ [ '/../.', '/' ],
+ [ '/./..', '/' ],
+ [ '/./.', '/' ],
+ [ 'b/../../a', '/a' ],
+ [ 'b/.././a', '/a' ],
+ [ 'b/./../a', '/a' ],
+ [ 'b/././a', 'b/a' ],
+ [ 'b/../../', '/' ],
+ [ 'b/.././', '/' ],
+ [ 'b/./../', '/' ],
+ [ 'b/././', 'b/' ],
+ [ 'b/../..', '/' ],
+ [ 'b/../.', '/' ],
+ [ 'b/./..', '/' ],
+ [ 'b/./.', 'b/' ],
+ [ '/b/../../a', '/a' ],
+ [ '/b/.././a', '/a' ],
+ [ '/b/./../a', '/a' ],
+ [ '/b/././a', '/b/a' ],
+ [ '/b/../../', '/' ],
+ [ '/b/.././', '/' ],
+ [ '/b/./../', '/' ],
+ [ '/b/././', '/b/' ],
+ [ '/b/../..', '/' ],
+ [ '/b/../.', '/' ],
+ [ '/b/./..', '/' ],
+ [ '/b/./.', '/b/' ],
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfShorthandToInteger
+ */
+class WfShorthandToIntegerTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider provideABunchOfShorthands
+ */
+ public function testWfShorthandToInteger( $input, $output, $description ) {
+ $this->assertEquals(
+ wfShorthandToInteger( $input ),
+ $output,
+ $description
+ );
+ }
+
+ public static function provideABunchOfShorthands() {
+ return [
+ [ '', -1, 'Empty string' ],
+ [ ' ', -1, 'String of spaces' ],
+ [ '1G', 1024 * 1024 * 1024, 'One gig uppercased' ],
+ [ '1g', 1024 * 1024 * 1024, 'One gig lowercased' ],
+ [ '1M', 1024 * 1024, 'One meg uppercased' ],
+ [ '1m', 1024 * 1024, 'One meg lowercased' ],
+ [ '1K', 1024, 'One kb uppercased' ],
+ [ '1k', 1024, 'One kb lowercased' ],
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfStringToBool
+ */
+class WfStringToBoolTest extends MediaWikiUnitTestCase {
+
+ public function getTestCases() {
+ return [
+ [ 'true', true ],
+ [ 'on', true ],
+ [ 'yes', true ],
+ [ 'TRUE', true ],
+ [ 'YeS', true ],
+ [ 'On', true ],
+ [ '1', true ],
+ [ '+1', true ],
+ [ '01', true ],
+ [ '-001', true ],
+ [ ' 1', true ],
+ [ '-1 ', true ],
+ [ '', false ],
+ [ '0', false ],
+ [ 'false', false ],
+ [ 'NO', false ],
+ [ 'NOT', false ],
+ [ 'never', false ],
+ [ '!&', false ],
+ [ '-0', false ],
+ [ '+0', false ],
+ [ 'forget about it', false ],
+ [ ' on', false ],
+ [ 'true ', false ],
+ ];
+ }
+
+ /**
+ * @dataProvider getTestCases
+ * @param string $str
+ * @param bool $bool
+ */
+ public function testStr2Bool( $str, $bool ) {
+ if ( $bool ) {
+ $this->assertTrue( wfStringToBool( $str ) );
+ } else {
+ $this->assertFalse( wfStringToBool( $str ) );
+ }
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @group GlobalFunctions
+ * @covers ::wfTimestamp
+ */
+class WfTimestampTest extends MediaWikiUnitTestCase {
+ /**
+ * @dataProvider provideNormalTimestamps
+ */
+ public function testNormalTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ public static function provideNormalTimestamps() {
+ $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+
+ return [
+ // TS_UNIX
+ [ $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ],
+ [ -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ],
+ [ $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ],
+ [ $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ],
+ [ $t + 0.01, TS_MW, '20010115123456', 'TS_UNIX float to TS_MW' ],
+
+ [ $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ],
+
+ // TS_MW
+ [ '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ],
+ [ '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ],
+ [ '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ],
+ [ '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ],
+
+ // TS_DB
+ [ '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ],
+ [ '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ],
+ [ '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ],
+ [
+ '2001-01-15 12:34:56',
+ TS_ISO_8601_BASIC,
+ '20010115T123456Z',
+ 'TS_DB to TS_ISO_8601_BASIC'
+ ],
+
+ # rfc2822 section 3.3
+ [ '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ],
+ [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
+ [
+ ' Mon, 15 Jan 2001 12:34:56 GMT',
+ TS_MW,
+ '20010115123456',
+ 'TS_RFC2822 with leading space to TS_MW'
+ ],
+ [
+ '15 Jan 2001 12:34:56 GMT',
+ TS_MW,
+ '20010115123456',
+ 'TS_RFC2822 without optional day-of-week to TS_MW'
+ ],
+
+ # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
+ # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
+ [ 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ],
+
+ # WSP = SP / HTAB ; rfc2234
+ [
+ "Mon, 15 Jan\x092001 12:34:56 GMT",
+ TS_MW,
+ '20010115123456',
+ 'TS_RFC2822 with HTAB to TS_MW'
+ ],
+ [
+ "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT",
+ TS_MW,
+ '20010115123456',
+ 'TS_RFC2822 with HTAB and SP to TS_MW'
+ ],
+ [
+ 'Sun, 6 Nov 94 08:49:37 GMT',
+ TS_MW,
+ '19941106084937',
+ 'TS_RFC2822 with obsolete year to TS_MW'
+ ],
+ ];
+ }
+
+ /**
+ * This test checks wfTimestamp() with values outside.
+ * It needs PHP 64 bits or PHP > 5.1.
+ * See r74778 and T27451
+ * @dataProvider provideOldTimestamps
+ */
+ public function testOldTimestamps( $input, $outputType, $output, $message ) {
+ $timestamp = wfTimestamp( $outputType, $input );
+ if ( substr( $output, 0, 1 ) === '/' ) {
+ // T66946: Day of the week calculations for very old
+ // timestamps varies from system to system.
+ $this->assertRegExp( $output, $timestamp, $message );
+ } else {
+ $this->assertEquals( $output, $timestamp, $message );
+ }
+ }
+
+ public static function provideOldTimestamps() {
+ return [
+ [
+ '19011213204554',
+ TS_RFC2822,
+ 'Fri, 13 Dec 1901 20:45:54 GMT',
+ 'Earliest time according to PHP documentation'
+ ],
+ [ '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ],
+ [ '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ],
+ [ '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ],
+ [ '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ],
+ [
+ '19011213204551',
+ TS_RFC2822,
+ 'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1'
+ ],
+ [ '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ],
+ [ '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ],
+ [ '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ],
+ [ '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ],
+ [ '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ],
+ [ '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ],
+ [
+ '0117-08-09 12:34:56',
+ TS_RFC2822,
+ '/, 09 Aug 0117 12:34:56 GMT$/',
+ 'Death of Roman Emperor [[Trajan]]'
+ ],
+
+ /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
+ [ '-58979923200', TS_RFC2822, '/, 01 Jan 0101 00:00:00 GMT$/', '1/1/101' ],
+ [ '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ],
+
+ /* It is not clear if we should generate a year 0 or not
+ * We are completely off RFC2822 requirement of year being
+ * 1900 or later.
+ */
+ [
+ '-62142076800',
+ TS_RFC2822,
+ 'Wed, 18 Oct 0000 00:00:00 GMT',
+ 'ISO 8601:2004 [[year 0]], also called [[1 BC]]'
+ ],
+ ];
+ }
+
+ /**
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
+ * @dataProvider provideHttpDates
+ */
+ public function testHttpDate( $input, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+ }
+
+ public static function provideHttpDates() {
+ return [
+ [ 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ],
+ [ 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ],
+ [ 'Sun Nov 6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ],
+ // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
+ [
+ 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626',
+ '20101122141242',
+ 'Netscape extension to HTTP/1.0'
+ ],
+ ];
+ }
+
+ /**
+ * There are a number of assumptions in our codebase where wfTimestamp()
+ * should give the current date but it is not given a 0 there. See r71751 CR
+ */
+ public function testTimestampParameter() {
+ $now = wfTimestamp( TS_UNIX );
+ // We check that wfTimestamp doesn't return false (error) and use a LessThan assert
+ // for the cases where the test is run in a second boundary.
+
+ $zero = wfTimestamp( TS_UNIX, 0 );
+ $this->assertNotEquals( false, $zero );
+ $this->assertLessThan( 5, $zero - $now );
+
+ $empty = wfTimestamp( TS_UNIX, '' );
+ $this->assertNotEquals( false, $empty );
+ $this->assertLessThan( 5, $empty - $now );
+
+ $null = wfTimestamp( TS_UNIX, null );
+ $this->assertNotEquals( false, $null );
+ $this->assertLessThan( 5, $null - $now );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * The function only need a string parameter and might react to IIS7.0
+ *
+ * @group GlobalFunctions
+ * @covers ::wfUrlencode
+ */
+class WfUrlencodeTest extends MediaWikiUnitTestCase {
+ # ### TESTS ##############################################################
+
+ /**
+ * @dataProvider provideURLS
+ */
+ public function testEncodingUrlWith( $input, $expected ) {
+ $this->verifyEncodingFor( 'Apache', $input, $expected );
+ }
+
+ /**
+ * @dataProvider provideURLS
+ */
+ public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
+ $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
+ }
+
+ # ### HELPERS #############################################################
+
+ /**
+ * Internal helper that actually run the test.
+ * Called by the public methods testEncodingUrlWith...()
+ */
+ private function verifyEncodingFor( $server, $input, $expectations ) {
+ $expected = $this->extractExpect( $server, $expectations );
+
+ // save up global
+ $old = $_SERVER['SERVER_SOFTWARE'] ?? null;
+ $_SERVER['SERVER_SOFTWARE'] = $server;
+ wfUrlencode( null );
+
+ // do the requested test
+ $this->assertEquals(
+ $expected,
+ wfUrlencode( $input ),
+ "Encoding '$input' for server '$server' should be '$expected'"
+ );
+
+ // restore global
+ if ( $old === null ) {
+ unset( $_SERVER['SERVER_SOFTWARE'] );
+ } else {
+ $_SERVER['SERVER_SOFTWARE'] = $old;
+ }
+ wfUrlencode( null );
+ }
+
+ /**
+ * Interprets the provider array. Return expected value depending
+ * the HTTP server name.
+ */
+ private function extractExpect( $server, $expectations ) {
+ if ( is_string( $expectations ) ) {
+ return $expectations;
+ } elseif ( is_array( $expectations ) ) {
+ if ( !array_key_exists( $server, $expectations ) ) {
+ throw new MWException( __METHOD__ . " expectation does not have any "
+ . "value for server name $server. Check the provider array.\n" );
+ } else {
+ return $expectations[$server];
+ }
+ } else {
+ throw new MWException( __METHOD__ . " given invalid expectation for "
+ . "'$server'. Should be a string or an array [ <http server name> => <string> ].\n" );
+ }
+ }
+
+ # ### PROVIDERS ###########################################################
+
+ /**
+ * Format is either:
+ * [ 'input', 'expected' ];
+ * Or:
+ * [ 'input',
+ * [ 'Apache', 'expected' ],
+ * [ 'Microsoft-IIS/7', 'expected' ],
+ * ],
+ * If you want to add other HTTP server name, you will have to add a new
+ * testing method much like the testEncodingUrlWith() method above.
+ */
+ public static function provideURLS() {
+ return [
+ # ## RFC 1738 chars
+ // + is not safe
+ [ '+', '%2B' ],
+ // & and = not safe in queries
+ [ '&', '%26' ],
+ [ '=', '%3D' ],
+
+ [ ':', [
+ 'Apache' => ':',
+ 'Microsoft-IIS/7' => '%3A',
+ ] ],
+
+ // remaining chars do not need encoding
+ [
+ ';@$-_.!*',
+ ';@$-_.!*',
+ ],
+
+ # ## Other tests
+ // slash remain unchanged. %2F seems to break things
+ [ '/', '/' ],
+ // T105265
+ [ '~', '~' ],
+
+ // Other 'funnies' chars
+ [ '[]', '%5B%5D' ],
+ [ '<>', '%3C%3E' ],
+
+ // Apostrophe is encoded
+ [ '\'', '%27' ],
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Tests for the PathRouter parsing.
+ *
+ * @covers PathRouter
+ */
+class PathRouterTest extends MediaWikiUnitTestCase {
+
+ /**
+ * @var PathRouter
+ */
+ protected $basicRouter;
+
+ protected function setUp() {
+ parent::setUp();
+ $router = new PathRouter;
+ $router->add( "/wiki/$1" );
+ $this->basicRouter = $router;
+ }
+
+ public static function provideParse() {
+ $tests = [
+ // Basic path parsing
+ 'Basic path parsing' => [
+ "/wiki/$1",
+ "/wiki/Foo",
+ [ 'title' => "Foo" ]
+ ],
+ //
+ 'Loose path auto-$1: /$1' => [
+ "/",
+ "/Foo",
+ [ 'title' => "Foo" ]
+ ],
+ 'Loose path auto-$1: /wiki' => [
+ "/wiki",
+ "/wiki/Foo",
+ [ 'title' => "Foo" ]
+ ],
+ 'Loose path auto-$1: /wiki/' => [
+ "/wiki/",
+ "/wiki/Foo",
+ [ 'title' => "Foo" ]
+ ],
+ // Ensure that path is based on specificity, not order
+ 'Order, /$1 added first' => [
+ [ "/$1", "/a/$1", "/b/$1" ],
+ "/a/Foo",
+ [ 'title' => "Foo" ]
+ ],
+ 'Order, /$1 added last' => [
+ [ "/b/$1", "/a/$1", "/$1" ],
+ "/a/Foo",
+ [ 'title' => "Foo" ]
+ ],
+ // Handling of key based arrays with a url parameter
+ 'Key based array' => [
+ [ [
+ 'path' => [ 'edit' => "/edit/$1" ],
+ 'params' => [ 'action' => '$key' ],
+ ] ],
+ "/edit/Foo",
+ [ 'title' => "Foo", 'action' => 'edit' ]
+ ],
+ // Additional parameter
+ 'Basic $2' => [
+ [ [
+ 'path' => '/$2/$1',
+ 'params' => [ 'test' => '$2' ]
+ ] ],
+ "/asdf/Foo",
+ [ 'title' => "Foo", 'test' => 'asdf' ]
+ ],
+ ];
+ // Shared patterns for restricted value parameter tests
+ $restrictedPatterns = [
+ [
+ 'path' => '/$2/$1',
+ 'params' => [ 'test' => '$2' ],
+ 'options' => [ '$2' => [ 'a', 'b' ] ]
+ ],
+ [
+ 'path' => '/$2/$1',
+ 'params' => [ 'test2' => '$2' ],
+ 'options' => [ '$2' => 'c' ]
+ ],
+ '/$1'
+ ];
+ $tests += [
+ // Restricted value parameter tests
+ 'Restricted 1' => [
+ $restrictedPatterns,
+ "/asdf/Foo",
+ [ 'title' => "asdf/Foo" ]
+ ],
+ 'Restricted 2' => [
+ $restrictedPatterns,
+ "/a/Foo",
+ [ 'title' => "Foo", 'test' => 'a' ]
+ ],
+ 'Restricted 3' => [
+ $restrictedPatterns,
+ "/c/Foo",
+ [ 'title' => "Foo", 'test2' => 'c' ]
+ ],
+
+ // Callback test
+ 'Callback' => [
+ [ [
+ 'path' => "/$1",
+ 'params' => [ 'a' => 'b', 'data:foo' => 'bar' ],
+ 'options' => [ 'callback' => [ __CLASS__, 'callbackForTest' ] ]
+ ] ],
+ '/Foo',
+ [
+ 'title' => "Foo",
+ 'x' => 'Foo',
+ 'a' => 'b',
+ 'foo' => 'bar'
+ ]
+ ],
+
+ // Test to ensure that matches are not made if a parameter expects nonexistent input
+ 'Fail' => [
+ [ [
+ 'path' => "/wiki/$1",
+ 'params' => [ 'title' => "$1$2" ],
+ ] ],
+ "/wiki/A",
+ []
+ ],
+
+ // Make sure the router handles titles like Special:Recentchanges correctly
+ 'Special title' => [
+ "/wiki/$1",
+ "/wiki/Special:Recentchanges",
+ [ 'title' => "Special:Recentchanges" ]
+ ],
+
+ // Make sure the router decodes urlencoding properly
+ 'URL encoding' => [
+ "/wiki/$1",
+ "/wiki/Title_With%20Space",
+ [ 'title' => "Title_With Space" ]
+ ],
+
+ // Double slash and dot expansion
+ 'Double slash in prefix' => [
+ '/wiki/$1',
+ '//wiki/Foo',
+ [ 'title' => 'Foo' ]
+ ],
+ 'Double slash at start of $1' => [
+ '/wiki/$1',
+ '/wiki//Foo',
+ [ 'title' => '/Foo' ]
+ ],
+ 'Double slash in middle of $1' => [
+ '/wiki/$1',
+ '/wiki/.hack//SIGN',
+ [ 'title' => '.hack//SIGN' ]
+ ],
+ 'Dots removed 1' => [
+ '/wiki/$1',
+ '/x/../wiki/Foo',
+ [ 'title' => 'Foo' ]
+ ],
+ 'Dots removed 2' => [
+ '/wiki/$1',
+ '/./wiki/Foo',
+ [ 'title' => 'Foo' ]
+ ],
+ 'Dots retained 1' => [
+ '/wiki/$1',
+ '/wiki/../wiki/Foo',
+ [ 'title' => '../wiki/Foo' ]
+ ],
+ 'Dots retained 2' => [
+ '/wiki/$1',
+ '/wiki/./Foo',
+ [ 'title' => './Foo' ]
+ ],
+ 'Triple slash' => [
+ '/wiki/$1',
+ '///wiki/Foo',
+ [ 'title' => 'Foo' ]
+ ],
+ // '..' only traverses one slash, see e.g. RFC 3986
+ 'Dots traversing double slash 1' => [
+ '/wiki/$1',
+ '/a//b/../../wiki/Foo',
+ []
+ ],
+ 'Dots traversing double slash 2' => [
+ '/wiki/$1',
+ '/a//b/../../../wiki/Foo',
+ [ 'title' => 'Foo' ]
+ ],
+ ];
+
+ // Make sure the router doesn't break on special characters like $ used in regexp replacements
+ foreach ( [ "$", "$1", "\\", "\\$1" ] as $char ) {
+ $tests["Regexp character $char"] = [
+ "/wiki/$1",
+ "/wiki/$char",
+ [ 'title' => "$char" ]
+ ];
+ }
+
+ $tests += [
+ // Make sure the router handles characters like +&() properly
+ "Special characters" => [
+ "/wiki/$1",
+ "/wiki/Plus+And&Dollar\\Stuff();[]{}*",
+ [ 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ],
+ ],
+
+ // Make sure the router handles unicode characters correctly
+ "Unicode 1" => [
+ "/wiki/$1",
+ "/wiki/Spécial:Modifications_récentes" ,
+ [ 'title' => "Spécial:Modifications_récentes" ],
+ ],
+
+ "Unicode 2" => [
+ "/wiki/$1",
+ "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes",
+ [ 'title' => "Spécial:Modifications_récentes" ],
+ ]
+ ];
+
+ // Ensure the router doesn't choke on long paths.
+ $lorem = "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_" .
+ "tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_" .
+ "nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._" .
+ "Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_" .
+ "eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_" .
+ "in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum.";
+
+ $tests += [
+ "Long path" => [
+ "/wiki/$1",
+ "/wiki/$lorem",
+ [ 'title' => $lorem ]
+ ],
+
+ // Ensure that the php passed site of parameter values are not urldecoded
+ "Pattern urlencoding" => [
+ [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => '%20:$1' ] ] ],
+ "/wiki/Foo",
+ [ 'title' => '%20:Foo' ]
+ ],
+
+ // Ensure that raw parameter values do not have any variable replacements or urldecoding
+ "Raw param value" => [
+ [ [ 'path' => "/wiki/$1", 'params' => [ 'title' => [ 'value' => 'bar%20$1' ] ] ] ],
+ "/wiki/Foo",
+ [ 'title' => 'bar%20$1' ]
+ ]
+ ];
+
+ return $tests;
+ }
+
+ /**
+ * Test path parsing
+ * @dataProvider provideParse
+ */
+ public function testParse( $patterns, $path, $expected ) {
+ $patterns = (array)$patterns;
+
+ $router = new PathRouter;
+ foreach ( $patterns as $pattern ) {
+ if ( is_array( $pattern ) ) {
+ $router->add( $pattern['path'], $pattern['params'] ?? [],
+ $pattern['options'] ?? [] );
+ } else {
+ $router->add( $pattern );
+ }
+ }
+ $matches = $router->parse( $path );
+ $this->assertEquals( $matches, $expected );
+ }
+
+ public static function callbackForTest( &$matches, $data ) {
+ $matches['x'] = $data['$1'];
+ $matches['foo'] = $data['foo'];
+ }
+
+ public static function provideWeight() {
+ return [
+ [ '/Foo', [ 'title' => 'Foo' ] ],
+ [ '/Bar', [ 'ping' => 'pong' ] ],
+ [ '/Baz', [ 'marco' => 'polo' ] ],
+ [ '/asdf-foo', [ 'title' => 'qwerty-foo' ] ],
+ [ '/qwerty-bar', [ 'title' => 'asdf-bar' ] ],
+ [ '/a/Foo', [ 'title' => 'Foo' ] ],
+ [ '/asdf/Foo', [ 'title' => 'Foo' ] ],
+ [ '/qwerty/Foo', [ 'title' => 'Foo', 'qwerty' => 'qwerty' ] ],
+ [ '/baz/Foo', [ 'title' => 'Foo', 'unrestricted' => 'baz' ] ],
+ [ '/y/Foo', [ 'title' => 'Foo', 'restricted-to-y' => 'y' ] ],
+ ];
+ }
+
+ /**
+ * Test to ensure weight of paths is handled correctly
+ * @dataProvider provideWeight
+ */
+ public function testWeight( $path, $expected ) {
+ $router = new PathRouter;
+ $router->addStrict( "/Bar", [ 'ping' => 'pong' ] );
+ $router->add( "/asdf-$1", [ 'title' => 'qwerty-$1' ] );
+ $router->add( "/$1" );
+ $router->add( "/qwerty-$1", [ 'title' => 'asdf-$1' ] );
+ $router->addStrict( "/Baz", [ 'marco' => 'polo' ] );
+ $router->add( "/a/$1" );
+ $router->add( "/asdf/$1" );
+ $router->add( "/$2/$1", [ 'unrestricted' => '$2' ] );
+ $router->add( [ 'qwerty' => "/qwerty/$1" ], [ 'qwerty' => '$key' ] );
+ $router->add( "/$2/$1", [ 'restricted-to-y' => '$2' ], [ '$2' => 'y' ] );
+
+ $this->assertEquals( $router->parse( $path ), $expected );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use ArrayIterator;
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWikiUnitTestCase;
+
+/** @covers \MediaWiki\Rest\ResponseFactory */
+class ResponseFactoryTest extends MediaWikiUnitTestCase {
+ public static function provideEncodeJson() {
+ return [
+ [ (object)[], '{}' ],
+ [ '/', '"/"' ],
+ [ '£', '"£"' ],
+ [ [], '[]' ],
+ ];
+ }
+
+ /** @dataProvider provideEncodeJson */
+ public function testEncodeJson( $input, $expected ) {
+ $rf = new ResponseFactory;
+ $this->assertSame( $expected, $rf->encodeJson( $input ) );
+ }
+
+ public function testCreateJson() {
+ $rf = new ResponseFactory;
+ $response = $rf->createJson( [] );
+ $response->getBody()->rewind();
+ $this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
+ $this->assertSame( '[]', $response->getBody()->getContents() );
+ // Make sure getSize() is functional, since testCreateNoContent() depends on it
+ $this->assertSame( 2, $response->getBody()->getSize() );
+ }
+
+ public function testCreateNoContent() {
+ $rf = new ResponseFactory;
+ $response = $rf->createNoContent();
+ $this->assertSame( [], $response->getHeader( 'Content-Type' ) );
+ $this->assertSame( 0, $response->getBody()->getSize() );
+ $this->assertSame( 204, $response->getStatusCode() );
+ }
+
+ public function testCreatePermanentRedirect() {
+ $rf = new ResponseFactory;
+ $response = $rf->createPermanentRedirect( 'http://www.example.com/' );
+ $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+ $this->assertSame( 301, $response->getStatusCode() );
+ }
+
+ public function testCreateLegacyTemporaryRedirect() {
+ $rf = new ResponseFactory;
+ $response = $rf->createLegacyTemporaryRedirect( 'http://www.example.com/' );
+ $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+ $this->assertSame( 302, $response->getStatusCode() );
+ }
+
+ public function testCreateTemporaryRedirect() {
+ $rf = new ResponseFactory;
+ $response = $rf->createTemporaryRedirect( 'http://www.example.com/' );
+ $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+ $this->assertSame( 307, $response->getStatusCode() );
+ }
+
+ public function testCreateSeeOther() {
+ $rf = new ResponseFactory;
+ $response = $rf->createSeeOther( 'http://www.example.com/' );
+ $this->assertSame( [ 'http://www.example.com/' ], $response->getHeader( 'Location' ) );
+ $this->assertSame( 303, $response->getStatusCode() );
+ }
+
+ public function testCreateNotModified() {
+ $rf = new ResponseFactory;
+ $response = $rf->createNotModified();
+ $this->assertSame( 0, $response->getBody()->getSize() );
+ $this->assertSame( 304, $response->getStatusCode() );
+ }
+
+ /** @expectedException \InvalidArgumentException */
+ public function testCreateHttpErrorInvalid() {
+ $rf = new ResponseFactory;
+ $rf->createHttpError( 200 );
+ }
+
+ public function testCreateHttpError() {
+ $rf = new ResponseFactory;
+ $response = $rf->createHttpError( 415, [ 'message' => '...' ] );
+ $this->assertSame( 415, $response->getStatusCode() );
+ $body = $response->getBody();
+ $body->rewind();
+ $data = json_decode( $body->getContents(), true );
+ $this->assertSame( 415, $data['httpCode'] );
+ $this->assertSame( '...', $data['message'] );
+ }
+
+ public function testCreateFromExceptionUnlogged() {
+ $rf = new ResponseFactory;
+ $response = $rf->createFromException( new HttpException( 'hello', 415 ) );
+ $this->assertSame( 415, $response->getStatusCode() );
+ $body = $response->getBody();
+ $body->rewind();
+ $data = json_decode( $body->getContents(), true );
+ $this->assertSame( 415, $data['httpCode'] );
+ $this->assertSame( 'hello', $data['message'] );
+ }
+
+ public function testCreateFromExceptionLogged() {
+ $rf = new ResponseFactory;
+ $response = $rf->createFromException( new \Exception( "hello", 415 ) );
+ $this->assertSame( 500, $response->getStatusCode() );
+ $body = $response->getBody();
+ $body->rewind();
+ $data = json_decode( $body->getContents(), true );
+ $this->assertSame( 500, $data['httpCode'] );
+ $this->assertSame( 'Error: exception of type Exception', $data['message'] );
+ }
+
+ public static function provideCreateFromReturnValue() {
+ return [
+ [ 'hello', '{"value":"hello"}' ],
+ [ true, '{"value":true}' ],
+ [ [ 'x' => 'y' ], '{"x":"y"}' ],
+ [ [ 'x', 'y' ], '["x","y"]' ],
+ [ [ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
+ [ (object)[ 'a', 'x' => 'y' ], '{"0":"a","x":"y"}' ],
+ [ [], '[]' ],
+ [ (object)[], '{}' ],
+ ];
+ }
+
+ /** @dataProvider provideCreateFromReturnValue */
+ public function testCreateFromReturnValue( $input, $expected ) {
+ $rf = new ResponseFactory;
+ $response = $rf->createFromReturnValue( $input );
+ $body = $response->getBody();
+ $body->rewind();
+ $this->assertSame( $expected, $body->getContents() );
+ }
+
+ /** @expectedException \InvalidArgumentException */
+ public function testCreateFromReturnValueInvalid() {
+ $rf = new ResponseFactory;
+ $rf->createFromReturnValue( new ArrayIterator );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use MediaWikiUnitTestCase;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends MediaWikiUnitTestCase {
+
+ private function makeTitleObject( $ns ) {
+ /** @var Title|MockObject $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->method( 'getNamespace' )
+ ->willReturn( $ns );
+
+ return $title;
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new MainSlotRoleHandler( [] );
+ $this->assertSame( 'main', $handler->getRole() );
+ $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+ */
+ public function testFetDefaultModel() {
+ $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+ // For the main handler, the namespace determins the default model
+ $titleMain = $this->makeTitleObject( NS_MAIN );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+ $title100 = $this->makeTitleObject( 100 );
+ $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ // For the main handler, (nearly) all models are allowed
+ $title = $this->makeTitleObject( NS_MAIN );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ $this->assertTrue( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\IncompleteRevisionException;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
+use MediaWikiUnitTestCase;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRecord
+ */
+class SlotRecordTest extends MediaWikiUnitTestCase {
+
+ private function makeRow( $data = [] ) {
+ $data = $data + [
+ 'slot_id' => 1234,
+ 'slot_content_id' => 33,
+ 'content_size' => '5',
+ 'content_sha1' => 'someHash',
+ 'content_address' => 'tt:456',
+ 'model_name' => CONTENT_MODEL_WIKITEXT,
+ 'format_name' => CONTENT_FORMAT_WIKITEXT,
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '1',
+ 'role_name' => 'myRole',
+ ];
+ return (object)$data;
+ }
+
+ public function testCompleteConstruction() {
+ $row = $this->makeRow();
+ $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+ $this->assertTrue( $record->hasAddress() );
+ $this->assertTrue( $record->hasContentId() );
+ $this->assertTrue( $record->hasRevision() );
+ $this->assertTrue( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getText() );
+ $this->assertSame( 5, $record->getSize() );
+ $this->assertSame( 'someHash', $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 1, $record->getOrigin() );
+ $this->assertSame( 'tt:456', $record->getAddress() );
+ $this->assertSame( 33, $record->getContentId() );
+ $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function testConstructionDeferred() {
+ $row = $this->makeRow( [
+ 'content_size' => null, // to be computed
+ 'content_sha1' => null, // to be computed
+ 'format_name' => function () {
+ return CONTENT_FORMAT_WIKITEXT;
+ },
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '2',
+ 'slot_content_id' => function () {
+ return null;
+ },
+ ] );
+
+ $content = function () {
+ return new WikitextContent( 'A' );
+ };
+
+ $record = new SlotRecord( $row, $content );
+
+ $this->assertTrue( $record->hasAddress() );
+ $this->assertTrue( $record->hasRevision() );
+ $this->assertFalse( $record->hasContentId() );
+ $this->assertFalse( $record->isInherited() );
+ $this->assertSame( 'A', $record->getContent()->getText() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotEmpty( $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 'tt:456', $record->getAddress() );
+ $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function testNewUnsaved() {
+ $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
+
+ $this->assertFalse( $record->hasAddress() );
+ $this->assertFalse( $record->hasContentId() );
+ $this->assertFalse( $record->hasRevision() );
+ $this->assertFalse( $record->isInherited() );
+ $this->assertFalse( $record->hasOrigin() );
+ $this->assertSame( 'A', $record->getContent()->getText() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotEmpty( $record->getSha1() );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+ $this->assertSame( 'myRole', $record->getRole() );
+ }
+
+ public function provideInvalidConstruction() {
+ yield 'both null' => [ null, null ];
+ yield 'null row' => [ null, new WikitextContent( 'A' ) ];
+ yield 'array row' => [ [], new WikitextContent( 'A' ) ];
+ yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
+ yield 'null content' => [ (object)[], null ];
+ }
+
+ /**
+ * @dataProvider provideInvalidConstruction
+ */
+ public function testInvalidConstruction( $row, $content ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new SlotRecord( $row, $content );
+ }
+
+ public function testGetContentId_fails() {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getContentId();
+ }
+
+ public function testGetAddress_fails() {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getAddress();
+ }
+
+ public function provideIncomplete() {
+ $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ yield 'unsaved' => [ $unsaved ];
+
+ $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $inherited = SlotRecord::newInherited( $parent );
+ yield 'inherited' => [ $inherited ];
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetRevision_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getRevision();
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetOrigin_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getOrigin();
+ }
+
+ public function provideHashStability() {
+ yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
+ yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
+ }
+
+ /**
+ * @dataProvider provideHashStability
+ */
+ public function testHashStability( $text, $hash ) {
+ // Changing the output of the hash function will break things horribly!
+
+ $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
+
+ $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
+ $this->assertSame( $hash, $record->getSha1() );
+ }
+
+ public function testHashComputed() {
+ $row = $this->makeRow();
+ $row->content_sha1 = '';
+
+ $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+ $this->assertNotEmpty( $rec->getSha1() );
+ }
+
+ public function testNewWithSuppressedContent() {
+ $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $output = SlotRecord::newWithSuppressedContent( $input );
+
+ $this->setExpectedException( SuppressedDataException::class );
+ $output->getContent();
+ }
+
+ public function testNewInherited() {
+ $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
+ $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+ // This would happen while doing an edit, before saving revision meta-data.
+ $inherited = SlotRecord::newInherited( $parent );
+
+ $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
+ $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
+ $this->assertSame( $parent->getContent(), $inherited->getContent() );
+ $this->assertTrue( $inherited->isInherited() );
+ $this->assertTrue( $inherited->hasOrigin() );
+ $this->assertFalse( $inherited->hasRevision() );
+
+ // make sure we didn't mess with the internal state of $parent
+ $this->assertFalse( $parent->isInherited() );
+ $this->assertSame( 7, $parent->getRevision() );
+
+ // This would happen while doing an edit, after saving the revision meta-data
+ // and content meta-data.
+ $saved = SlotRecord::newSaved(
+ 10,
+ $inherited->getContentId(),
+ $inherited->getAddress(),
+ $inherited
+ );
+ $this->assertSame( $parent->getContentId(), $saved->getContentId() );
+ $this->assertSame( $parent->getAddress(), $saved->getAddress() );
+ $this->assertSame( $parent->getContent(), $saved->getContent() );
+ $this->assertTrue( $saved->isInherited() );
+ $this->assertTrue( $saved->hasRevision() );
+ $this->assertSame( 10, $saved->getRevision() );
+
+ // make sure we didn't mess with the internal state of $parent or $inherited
+ $this->assertSame( 7, $parent->getRevision() );
+ $this->assertFalse( $inherited->hasRevision() );
+ }
+
+ public function testNewSaved() {
+ // This would happen while doing an edit, before saving revision meta-data.
+ $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+ // This would happen while doing an edit, after saving the revision meta-data
+ // and content meta-data.
+ $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
+ $this->assertFalse( $saved->isInherited() );
+ $this->assertTrue( $saved->hasOrigin() );
+ $this->assertTrue( $saved->hasRevision() );
+ $this->assertTrue( $saved->hasAddress() );
+ $this->assertTrue( $saved->hasContentId() );
+ $this->assertSame( 'theNewAddress', $saved->getAddress() );
+ $this->assertSame( 20, $saved->getContentId() );
+ $this->assertSame( 'A', $saved->getContent()->getText() );
+ $this->assertSame( 10, $saved->getRevision() );
+ $this->assertSame( 10, $saved->getOrigin() );
+
+ // make sure we didn't mess with the internal state of $unsaved
+ $this->assertFalse( $unsaved->hasAddress() );
+ $this->assertFalse( $unsaved->hasContentId() );
+ $this->assertFalse( $unsaved->hasRevision() );
+ }
+
+ public function provideNewSaved_LogicException() {
+ $freshRow = $this->makeRow( [
+ 'content_id' => 10,
+ 'content_address' => 'address:1',
+ 'slot_origin' => 1,
+ 'slot_revision_id' => 1,
+ ] );
+
+ $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
+ yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
+ yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
+ yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
+
+ $inheritedRow = $this->makeRow( [
+ 'content_id' => null,
+ 'content_address' => null,
+ 'slot_origin' => 0,
+ 'slot_revision_id' => 1,
+ ] );
+
+ $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
+ yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
+ }
+
+ /**
+ * @dataProvider provideNewSaved_LogicException
+ */
+ public function testNewSaved_LogicException(
+ $revisionId,
+ $contentId,
+ $contentAddress,
+ SlotRecord $protoSlot
+ ) {
+ $this->setExpectedException( LogicException::class );
+ SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+ }
+
+ public function provideNewSaved_InvalidArgumentException() {
+ $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+ yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
+ yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
+ yield 'bad content address' => [ 7, 5, 77, $unsaved ];
+ }
+
+ /**
+ * @dataProvider provideNewSaved_InvalidArgumentException
+ */
+ public function testNewSaved_InvalidArgumentException(
+ $revisionId,
+ $contentId,
+ $contentAddress,
+ SlotRecord $protoSlot
+ ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+ }
+
+ public function provideHasSameContent() {
+ $fail = function () {
+ self::fail( 'There should be no need to actually load the content.' );
+ };
+
+ $a100a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a100a1b = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a100null = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => null,
+ ]
+ ),
+ $fail
+ );
+ $a100a2 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a2',
+ ]
+ ),
+ $fail
+ );
+ $b100a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'B',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a1',
+ ]
+ ),
+ $fail
+ );
+ $a200a1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 200,
+ 'content_sha1' => 'hash-a',
+ 'content_address' => 'xxx:a2',
+ ]
+ ),
+ $fail
+ );
+ $a100x1 = new SlotRecord(
+ $this->makeRow(
+ [
+ 'model_name' => 'A',
+ 'content_size' => 100,
+ 'content_sha1' => 'hash-x',
+ 'content_address' => 'xxx:x1',
+ ]
+ ),
+ $fail
+ );
+
+ yield 'same instance' => [ $a100a1, $a100a1, true ];
+ yield 'no address' => [ $a100a1, $a100null, true ];
+ yield 'same address' => [ $a100a1, $a100a1b, true ];
+ yield 'different address' => [ $a100a1, $a100a2, true ];
+ yield 'different model' => [ $a100a1, $b100a1, false ];
+ yield 'different size' => [ $a100a1, $a200a1, false ];
+ yield 'different hash' => [ $a100a1, $a100x1, false ];
+ }
+
+ /**
+ * @dataProvider provideHasSameContent
+ */
+ public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
+ $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
+ $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @author Addshore
+ * @covers TitleArrayFromResult
+ */
+class TitleArrayFromResultTest extends MediaWikiUnitTestCase {
+
+ private function getMockResultWrapper( $row = null, $numRows = 1 ) {
+ $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
+ ->disableOriginalConstructor();
+
+ $resultWrapper = $resultWrapper->getMock();
+ $resultWrapper->expects( $this->atLeastOnce() )
+ ->method( 'current' )
+ ->will( $this->returnValue( $row ) );
+ $resultWrapper->expects( $this->any() )
+ ->method( 'numRows' )
+ ->will( $this->returnValue( $numRows ) );
+
+ return $resultWrapper;
+ }
+
+ private function getRowWithTitle( $namespace = 3, $title = 'foo' ) {
+ $row = new stdClass();
+ $row->page_namespace = $namespace;
+ $row->page_title = $title;
+ return $row;
+ }
+
+ /**
+ * @covers TitleArrayFromResult::__construct
+ */
+ public function testConstructionWithFalseRow() {
+ $row = false;
+ $resultWrapper = $this->getMockResultWrapper( $row );
+
+ $object = new TitleArrayFromResult( $resultWrapper );
+
+ $this->assertEquals( $resultWrapper, $object->res );
+ $this->assertSame( 0, $object->key );
+ $this->assertEquals( $row, $object->current );
+ }
+
+ /**
+ * @covers TitleArrayFromResult::__construct
+ */
+ public function testConstructionWithRow() {
+ $namespace = 0;
+ $title = 'foo';
+ $row = $this->getRowWithTitle( $namespace, $title );
+ $resultWrapper = $this->getMockResultWrapper( $row );
+
+ $object = new TitleArrayFromResult( $resultWrapper );
+
+ $this->assertEquals( $resultWrapper, $object->res );
+ $this->assertSame( 0, $object->key );
+ $this->assertInstanceOf( Title::class, $object->current );
+ $this->assertEquals( $namespace, $object->current->mNamespace );
+ $this->assertEquals( $title, $object->current->mTextform );
+ }
+
+ public static function provideNumberOfRows() {
+ return [
+ [ 0 ],
+ [ 1 ],
+ [ 122 ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideNumberOfRows
+ * @covers TitleArrayFromResult::count
+ */
+ public function testCountWithVaryingValues( $numRows ) {
+ $object = new TitleArrayFromResult( $this->getMockResultWrapper(
+ $this->getRowWithTitle(),
+ $numRows
+ ) );
+ $this->assertEquals( $numRows, $object->count() );
+ }
+
+ /**
+ * @covers TitleArrayFromResult::current
+ */
+ public function testCurrentAfterConstruction() {
+ $namespace = 0;
+ $title = 'foo';
+ $row = $this->getRowWithTitle( $namespace, $title );
+ $object = new TitleArrayFromResult( $this->getMockResultWrapper( $row ) );
+ $this->assertInstanceOf( Title::class, $object->current() );
+ $this->assertEquals( $namespace, $object->current->mNamespace );
+ $this->assertEquals( $title, $object->current->mTextform );
+ }
+
+ public function provideTestValid() {
+ return [
+ [ $this->getRowWithTitle(), true ],
+ [ false, false ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideTestValid
+ * @covers TitleArrayFromResult::valid
+ */
+ public function testValid( $input, $expected ) {
+ $object = new TitleArrayFromResult( $this->getMockResultWrapper( $input ) );
+ $this->assertEquals( $expected, $object->valid() );
+ }
+
+ // @todo unit test for key()
+ // @todo unit test for next()
+ // @todo unit test for rewind()
+}
--- /dev/null
+<?php
+
+/**
+ * @covers WikiReference
+ */
+class WikiReferenceTest extends MediaWikiUnitTestCase {
+
+ public function provideGetDisplayName() {
+ return [
+ 'http' => [ 'foo.bar', 'http://foo.bar' ],
+ 'https' => [ 'foo.bar', 'http://foo.bar' ],
+
+ // apparently, this is the expected behavior
+ 'invalid' => [ 'purple kittens', 'purple kittens' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetDisplayName
+ */
+ public function testGetDisplayName( $expected, $canonicalServer ) {
+ $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
+ $this->assertEquals( $expected, $reference->getDisplayName() );
+ }
+
+ public function testGetCanonicalServer() {
+ $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
+ $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
+ }
+
+ public function provideGetCanonicalUrl() {
+ return [
+ 'no fragment' => [
+ 'https://acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ null
+ ],
+ 'empty fragment' => [
+ 'https://acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ ''
+ ],
+ 'fragment' => [
+ 'https://acme.com/wiki/Foo#Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar'
+ ],
+ 'double fragment' => [
+ 'https://acme.com/wiki/Foo#Bar%23Xus',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar#Xus'
+ ],
+ 'escaped fragment' => [
+ 'https://acme.com/wiki/Foo%23Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo#Bar',
+ null
+ ],
+ 'empty path' => [
+ 'https://acme.com/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/$1',
+ 'Foo',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetCanonicalUrl
+ */
+ public function testGetCanonicalUrl(
+ $expected, $canonicalServer, $server, $path, $page, $fragmentId
+ ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
+ }
+
+ /**
+ * @dataProvider provideGetCanonicalUrl
+ * @note getUrl is an alias for getCanonicalUrl
+ */
+ public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
+ }
+
+ public function provideGetFullUrl() {
+ return [
+ 'no fragment' => [
+ '//acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ null
+ ],
+ 'empty fragment' => [
+ '//acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ ''
+ ],
+ 'fragment' => [
+ '//acme.com/wiki/Foo#Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar'
+ ],
+ 'double fragment' => [
+ '//acme.com/wiki/Foo#Bar%23Xus',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar#Xus'
+ ],
+ 'escaped fragment' => [
+ '//acme.com/wiki/Foo%23Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo#Bar',
+ null
+ ],
+ 'empty path' => [
+ '//acme.com/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/$1',
+ 'Foo',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetFullUrl
+ */
+ public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Logger\Monolog;
+
+/**
+ * Flay per https://phabricator.wikimedia.org/T218688.
+ *
+ * @group Broken
+ * @covers \MediaWiki\Logger\Monolog\CeeFormatter
+ */
+class CeeFormatterTest extends \MediaWikiUnitTestCase {
+ public function testV1() {
+ $ls_formatter = new LogstashFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+ $cee_formatter = new CeeFormatter( 'app', 'system', null, 'ctx_', LogstashFormatter::V1 );
+ $record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
+ $this->assertSame(
+ $cee_formatter->format( $record ),
+ "@cee: " . $ls_formatter->format( $record ) );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends \MediaWikiUnitTestCase {
+
+ public function testGetDiff() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ $this->assertEquals( 'xxx|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( null, $newContent );
+ $this->assertEquals( '|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+ $this->assertEquals( 'xxx|', $diff );
+ }
+
+ public function testAddModules() {
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'addModules' ] )
+ ->getMock();
+ $output->expects( $this->once() )
+ ->method( 'addModules' )
+ ->with( 'foo' );
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $slotDiffRenderer->addModules( $output );
+ }
+
+ public function testGetExtraCacheKeys() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+ $this->assertSame( [ 'foo' ], $extraCacheKeys );
+ }
+
+}
--- /dev/null
+<?php
+
+use Wikimedia\Assert\ParameterTypeException;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers SlotDiffRenderer
+ */
+class SlotDiffRendererTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @dataProvider provideNormalizeContents
+ */
+ public function testNormalizeContents(
+ $oldContent, $newContent, $allowedClasses,
+ $expectedOldContent, $expectedNewContent, $expectedExceptionClass
+ ) {
+ $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->getMock();
+ try {
+ // __call needs help deciding which parameter to take by reference
+ call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
+ 'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
+ $this->assertEquals( $expectedOldContent, $oldContent );
+ $this->assertEquals( $expectedNewContent, $newContent );
+ } catch ( Exception $e ) {
+ if ( !$expectedExceptionClass ) {
+ throw $e;
+ }
+ $this->assertInstanceOf( $expectedExceptionClass, $e );
+ }
+ }
+
+ public function provideNormalizeContents() {
+ return [
+ 'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
+ 'left null' => [
+ null, new WikitextContent( 'abc' ), null,
+ new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
+ ],
+ 'right null' => [
+ new WikitextContent( 'def' ), null, null,
+ new WikitextContent( 'def' ), new WikitextContent( '' ), null,
+ ],
+ 'type filter' => [
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+ ],
+ 'type filter (subclass)' => [
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+ ],
+ 'type filter (null)' => [
+ new WikitextContent( 'abc' ), null, TextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
+ ],
+ 'type filter failure (left)' => [
+ new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+ null, null, ParameterTypeException::class,
+ ],
+ 'type filter failure (right)' => [
+ new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
+ null, null, ParameterTypeException::class,
+ ],
+ 'type filter (array syntax)' => [
+ new WikitextContent( 'abc' ), new JsonContent( 'def' ),
+ [ JsonContent::class, WikitextContent::class ],
+ new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
+ ],
+ 'type filter failure (array syntax)' => [
+ new WikitextContent( 'abc' ), new CssContent( 'def' ),
+ [ JsonContent::class, WikitextContent::class ],
+ null, null, ParameterTypeException::class,
+ ],
+ ];
+ }
+
+}
--- /dev/null
+<?php
+
+class FileBackendDBRepoWrapperTest extends MediaWikiUnitTestCase {
+ protected $backendName = 'foo-backend';
+ protected $repoName = 'pureTestRepo';
+
+ /**
+ * @dataProvider getBackendPathsProvider
+ * @covers FileBackendDBRepoWrapper::getBackendPaths
+ */
+ public function testGetBackendPaths(
+ $mocks,
+ $latest,
+ $dbReadsExpected,
+ $dbReturnValue,
+ $originalPath,
+ $expectedBackendPath,
+ $message ) {
+ list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
+
+ $dbMock->expects( $dbReadsExpected )
+ ->method( 'selectField' )
+ ->will( $this->returnValue( $dbReturnValue ) );
+
+ $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
+
+ $this->assertEquals(
+ $expectedBackendPath,
+ $newPaths[0],
+ $message );
+ }
+
+ public function getBackendPathsProvider() {
+ $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
+ $mocksForCaching = $this->getMocks();
+
+ return [
+ [
+ $mocksForCaching,
+ false,
+ $this->once(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'Public path translated correctly',
+ ],
+ [
+ $mocksForCaching,
+ false,
+ $this->never(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'LRU cache leveraged',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->once(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'Latest obtained',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->never(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-deleted/f/o/foobar.jpg',
+ $prefix . '-original/f/o/o/foobar',
+ 'Deleted path translated correctly',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->once(),
+ null,
+ $prefix . '-public/b/a/baz.jpg',
+ $prefix . '-public/b/a/baz.jpg',
+ 'Path left untouched if no sha1 can be found',
+ ],
+ ];
+ }
+
+ /**
+ * @covers FileBackendDBRepoWrapper::getFileContentsMulti
+ */
+ public function testGetFileContentsMulti() {
+ list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
+
+ $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
+ . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
+ $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
+ . '-public/f/o/foobar.jpg';
+
+ $dbMock->expects( $this->once() )
+ ->method( 'selectField' )
+ ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
+
+ $backendMock->expects( $this->once() )
+ ->method( 'getFileContentsMulti' )
+ ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
+
+ $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
+
+ $this->assertEquals(
+ [ $filenamePath => 'foo' ],
+ $result,
+ 'File contents paths translated properly'
+ );
+ }
+
+ protected function getMocks() {
+ $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
+ ->disableOriginalClone()
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $backendMock = $this->getMockBuilder( FSFileBackend::class )
+ ->setConstructorArgs( [ [
+ 'name' => $this->backendName,
+ 'wikiId' => wfWikiID()
+ ] ] )
+ ->getMock();
+
+ $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
+ ->setMethods( [ 'getDB' ] )
+ ->setConstructorArgs( [ [
+ 'backend' => $backendMock,
+ 'repoName' => $this->repoName,
+ 'dbHandleFactory' => null
+ ] ] )
+ ->getMock();
+
+ $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
+
+ return [ $dbMock, $backendMock, $wrapperMock ];
+ }
+}
--- /dev/null
+<?php
+
+/** @covers ForeignDBFile */
+class ForeignDBFileTest extends \MediaWikiUnitTestCase {
+
+ public function testShouldConstructCorrectInstanceFromTitle() {
+ $title = Title::makeTitle( NS_FILE, 'Awesome_file' );
+ $repoMock = $this->createMock( LocalRepo::class );
+
+ $file = ForeignDBFile::newFromTitle( $title, $repoMock );
+
+ $this->assertInstanceOf( ForeignDBFile::class, $file );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @covers HTMLCheckMatrix
+ */
+class HTMLCheckMatrixTest extends MediaWikiUnitTestCase {
+ private static $defaultOptions = [
+ 'rows' => [ 'r1', 'r2' ],
+ 'columns' => [ 'c1', 'c2' ],
+ 'fieldname' => 'test',
+ ];
+
+ public function testPlainInstantiation() {
+ try {
+ new HTMLCheckMatrix( [] );
+ } catch ( MWException $e ) {
+ $this->assertInstanceOf( HTMLFormFieldRequiredOptionsException::class, $e );
+ return;
+ }
+
+ $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
+ }
+
+ public function testInstantiationWithMinimumRequiredParameters() {
+ new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertTrue( true ); // form instantiation must throw exception on failure
+ }
+
+ public function testValidateCallsUserDefinedValidationCallback() {
+ $called = false;
+ $field = new HTMLCheckMatrix( self::$defaultOptions + [
+ 'validation-callback' => function () use ( &$called ) {
+ $called = true;
+
+ return false;
+ },
+ ] );
+ $this->assertEquals( false, $this->validate( $field, [] ) );
+ $this->assertTrue( $called );
+ }
+
+ public function testValidateRequiresArrayInput() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertEquals( false, $this->validate( $field, null ) );
+ $this->assertEquals( false, $this->validate( $field, true ) );
+ $this->assertEquals( false, $this->validate( $field, 'abc' ) );
+ $this->assertEquals( false, $this->validate( $field, new stdClass ) );
+ $this->assertEquals( true, $this->validate( $field, [] ) );
+ }
+
+ public function testValidateAllowsOnlyKnownTags() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertInstanceOf( Message::class, $this->validate( $field, [ 'foo' ] ) );
+ }
+
+ public function testValidateAcceptsPartialTagList() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertTrue( $this->validate( $field, [] ) );
+ $this->assertTrue( $this->validate( $field, [ 'c1-r1' ] ) );
+ $this->assertTrue( $this->validate( $field, [ 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ] ) );
+ }
+
+ /**
+ * This form object actually has no visibility into what happens later on, but essentially
+ * if the data submitted by the user passes validate the following is run:
+ * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
+ * $user->setOption( $k, $v );
+ * }
+ */
+ public function testValuesForcedOnRemainOn() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions + [
+ 'force-options-on' => [ 'c2-r1' ],
+ ] );
+ $expected = [
+ 'c1-r1' => false,
+ 'c1-r2' => false,
+ 'c2-r1' => true,
+ 'c2-r2' => false,
+ ];
+ $this->assertEquals( $expected, $field->filterDataForSubmit( [] ) );
+ }
+
+ public function testValuesForcedOffRemainOff() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions + [
+ 'force-options-off' => [ 'c1-r2', 'c2-r2' ],
+ ] );
+ $expected = [
+ 'c1-r1' => true,
+ 'c1-r2' => false,
+ 'c2-r1' => true,
+ 'c2-r2' => false,
+ ];
+ // array_keys on the result simulates submitting all fields checked
+ $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
+ }
+
+ protected function validate( HTMLFormField $field, $submitted ) {
+ return $field->validate(
+ $submitted,
+ [ self::$defaultOptions['fieldname'] => $submitted ]
+ );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers FormatJson
+ */
+class FormatJsonTest extends MediaWikiUnitTestCase {
+
+ /**
+ * Test data for testParseTryFixing.
+ *
+ * Some PHP interpreters use json-c rather than the JSON.org canonical
+ * parser to avoid being encumbered by the "shall be used for Good, not
+ * Evil" clause of the JSON.org parser's license. By default, json-c
+ * parses in a non-strict mode which allows trailing commas for array and
+ * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
+ * block is not always triggered. It however isn't lenient in exactly the
+ * same ways as our TRY_FIXING mode, so the assertions in this test are
+ * a bit more complicated than they ideally would be:
+ *
+ * Optional third argument: true if json-c parses the value without
+ * intervention, false otherwise. Defaults to true.
+ *
+ * Optional fourth argument: expected cannonical JSON serialization of
+ * json-c parsed result. Defaults to the second argument's value.
+ */
+ public static function provideParseTryFixing() {
+ return [
+ [ "[,]", '[]', false ],
+ [ "[ , ]", '[]', false ],
+ [ "[ , }", false ],
+ [ '[1],', false, true, '[1]' ],
+ [ "[1,]", '[1]' ],
+ [ "[1\n,]", '[1]' ],
+ [ "[1,\n]", '[1]' ],
+ [ "[1,]\n", '[1]' ],
+ [ "[1\n,\n]\n", '[1]' ],
+ [ '["a,",]', '["a,"]' ],
+ [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
+ // I wish we could parse this, but would need quote parsing
+ [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
+ [ '[1,,]', false, false, '[1]' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideParseTryFixing
+ * @param string $value
+ * @param string|bool $expected Expected result with strict parser
+ * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
+ * @param string|bool $expectedJsonc Expected result with lenient parser
+ * if different from the strict expectation
+ */
+ public function testParseTryFixing(
+ $value, $expected,
+ $jsoncParses = true, $expectedJsonc = null
+ ) {
+ // PHP5 results are always expected to have isGood() === false
+ $expectedGoodStatus = false;
+
+ // Check to see if json parser allows trailing commas
+ if ( json_decode( '[1,]' ) !== null ) {
+ // Use json-c specific expected result if provided
+ $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
+ // If json-c parses the value natively, expect isGood() === true
+ $expectedGoodStatus = $jsoncParses;
+ }
+
+ $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
+ $this->assertInstanceOf( Status::class, $st );
+ if ( $expected === false ) {
+ $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
+ } else {
+ $this->assertSame( $expectedGoodStatus, $st->isGood(),
+ 'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
+ );
+ $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
+ $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
+ $this->assertEquals( $expected, $val );
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiUnitTestCase {
+
+ protected $filePath;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->filePath = __DIR__ . '/../../../data/media/';
+ }
+
+ /**
+ * We also use this test to test padding bytes don't
+ * screw stuff up
+ *
+ * @param string $file Filename
+ *
+ * @dataProvider provideUtf8Comment
+ */
+ public function testUtf8Comment( $file ) {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+ $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
+ }
+
+ public static function provideUtf8Comment() {
+ return [
+ [ 'jpeg-comment-utf.jpg' ],
+ [ 'jpeg-padding-even.jpg' ],
+ [ 'jpeg-padding-odd.jpg' ],
+ ];
+ }
+
+ /** The file is iso-8859-1, but it should get auto converted */
+ public function testIso88591Comment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+ $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
+ }
+
+ /** Comment values that are non-textual (random binary junk) should not be shown.
+ * The example test file has a comment with a 0x5 byte in it which is a control character
+ * and considered binary junk for our purposes.
+ */
+ public function testBinaryCommentStripped() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+ $this->assertEmpty( $res['COM'] );
+ }
+
+ /* Very rarely a file can have multiple comments.
+ * Order of comments is based on order inside the file.
+ */
+ public function testMultipleComment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+ $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
+ }
+
+ public function testXMPExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+ public function testPSIRExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = '50686f746f73686f7020332e30003842494d04040000000'
+ . '000181c02190004746573741c02190003666f6f1c020000020004';
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+ }
+
+ public function testXMPExtractionAltAppId() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+ public function testIPTCHashComparisionNoHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-no-hash', $res );
+ }
+
+ public function testIPTCHashComparisionBadHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-bad-hash', $res );
+ }
+
+ public function testIPTCHashComparisionGoodHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-good-hash', $res );
+ }
+
+ public function testExifByteOrder() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+ $expected = 'BE';
+ $this->assertEquals( $expected, $res['byteOrder'] );
+ }
+
+ public function testInfiniteRead() {
+ // test file truncated right after a segment, which previously
+ // caused an infinite loop looking for the next segment byte.
+ // Should get past infinite loop and throw in wfUnpack()
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+ }
+
+ public function testInfiniteRead2() {
+ // test file truncated after a segment's marker and size, which
+ // would cause a seek past end of file. Seek past end of file
+ // doesn't actually fail, but prevents further reading and was
+ // devolving into the previous case (testInfiniteRead).
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+ }
+}
--- /dev/null
+<?php
+
+class ArticleTest extends MediaWikiUnitTestCase {
+
+ /**
+ * @var Title
+ */
+ private $title;
+ /**
+ * @var Article
+ */
+ private $article;
+
+ /** creates a title object and its article object */
+ protected function setUp() {
+ parent::setUp();
+ $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
+ $this->article = new Article( $this->title );
+ }
+
+ /** cleanup title object and its article object */
+ protected function tearDown() {
+ parent::tearDown();
+ $this->title = null;
+ $this->article = null;
+ }
+
+ /**
+ * @covers Article::__get
+ */
+ public function testImplementsGetMagic() {
+ $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
+ }
+
+ /**
+ * @depends testImplementsGetMagic
+ * @covers Article::__set
+ */
+ public function testImplementsSetMagic() {
+ $this->article->mLatest = 2;
+ $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
+ }
+
+ /**
+ * @covers Article::__get
+ * @covers Article::__set
+ */
+ public function testGetOrSetOnNewProperty() {
+ $this->article->ext_someNewProperty = 12;
+ $this->assertEquals( 12, $this->article->ext_someNewProperty,
+ "Article get/set magic on new field" );
+
+ $this->article->ext_someNewProperty = -8;
+ $this->assertEquals( -8, $this->article->ext_someNewProperty,
+ "Article get/set magic on update to new field" );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Session;
+
+use MediaWikiUnitTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\Token
+ */
+class TokenTest extends MediaWikiUnitTestCase {
+
+ public function testBasics() {
+ $token = $this->getMockBuilder( Token::class )
+ ->setMethods( [ 'toStringAtTimestamp' ] )
+ ->setConstructorArgs( [ 'sekret', 'salty', true ] )
+ ->getMock();
+ $token->expects( $this->any() )->method( 'toStringAtTimestamp' )
+ ->will( $this->returnValue( 'faketoken+\\' ) );
+
+ $this->assertSame( 'faketoken+\\', $token->toString() );
+ $this->assertSame( 'faketoken+\\', (string)$token );
+ $this->assertTrue( $token->wasNew() );
+
+ $token = new Token( 'sekret', 'salty', false );
+ $this->assertFalse( $token->wasNew() );
+ }
+
+ public function testToStringAtTimestamp() {
+ $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
+
+ $this->assertSame(
+ 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\',
+ $token->toStringAtTimestamp( 1447362018 )
+ );
+ $this->assertSame(
+ 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\',
+ $token->toStringAtTimestamp( 1447362026 )
+ );
+ }
+
+ public function testGetTimestamp() {
+ $this->assertSame(
+ 1447362018, Token::getTimestamp( 'd9ade0c7d4349e9df9094e61c33a5a0d5644fde2+\\' )
+ );
+ $this->assertSame(
+ 1447362026, Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea+\\' )
+ );
+ $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
+ $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9176c224cfb400d43be+\\' ) );
+
+ $this->assertNull( Token::getTimestamp( 'ee2f7a2488dea9x76c224cfb400d43be5644fdea+\\' ) );
+ }
+
+ public function testMatch() {
+ $token = TestingAccessWrapper::newFromObject( new Token( 'sekret', 'salty', false ) );
+
+ $test = $token->toStringAtTimestamp( time() - 10 );
+ $this->assertTrue( $token->match( $test ) );
+ $this->assertTrue( $token->match( $test, 12 ) );
+ $this->assertFalse( $token->match( $test, 8 ) );
+
+ $this->assertFalse( $token->match( 'ee2f7a2488dea9176c224cfb400d43be5644fdea-\\' ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Copyright (C) 2017 Kunal Mehta <legoktm@member.fsf.org>
+ *
+ * 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.
+ *
+ */
+
+use MediaWiki\Shell\FirejailCommand;
+use MediaWiki\Shell\Shell;
+use Wikimedia\TestingAccessWrapper;
+
+class FirejailCommandTest extends MediaWikiUnitTestCase {
+
+ public function provideBuildFinalCommand() {
+ global $IP;
+ // phpcs:ignore Generic.Files.LineLength
+ $env = "'MW_INCLUDE_STDERR=;MW_CPU_LIMIT=180; MW_CGROUP='\'''\''; MW_MEM_LIMIT=307200; MW_FILE_SIZE_LIMIT=102400; MW_WALL_CLOCK_LIMIT=180; MW_USE_LOG_PIPE=yes'";
+ $limit = "/bin/bash '$IP/includes/shell/limit.sh'";
+ $profile = "--profile=$IP/includes/shell/firejail.profile";
+ $blacklist = '--blacklist=' . realpath( MW_CONFIG_FILE );
+ $default = "$blacklist --noroot --seccomp --private-dev";
+ return [
+ [
+ 'No restrictions',
+ 'ls', 0, "$limit ''\''ls'\''' $env"
+ ],
+ [
+ 'default restriction',
+ 'ls', Shell::RESTRICT_DEFAULT,
+ "$limit 'firejail --quiet $profile $default -- '\''ls'\''' $env"
+ ],
+ [
+ 'no network',
+ 'ls', Shell::NO_NETWORK,
+ "$limit 'firejail --quiet $profile --net=none -- '\''ls'\''' $env"
+ ],
+ [
+ 'default restriction & no network',
+ 'ls', Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK,
+ "$limit 'firejail --quiet $profile $default --net=none -- '\''ls'\''' $env"
+ ],
+ [
+ 'seccomp',
+ 'ls', Shell::SECCOMP,
+ "$limit 'firejail --quiet $profile --seccomp -- '\''ls'\''' $env"
+ ],
+ [
+ 'seccomp & no execve',
+ 'ls', Shell::SECCOMP | Shell::NO_EXECVE,
+ "$limit 'firejail --quiet $profile --shell=none --seccomp=execve -- '\''ls'\''' $env"
+ ],
+ ];
+ }
+
+ /**
+ * @covers \MediaWiki\Shell\FirejailCommand::buildFinalCommand()
+ * @dataProvider provideBuildFinalCommand
+ */
+ public function testBuildFinalCommand( $desc, $params, $flags, $expected ) {
+ $command = new FirejailCommand( 'firejail' );
+ $command
+ ->params( $params )
+ ->restrict( $flags );
+ $wrapper = TestingAccessWrapper::newFromObject( $command );
+ $output = $wrapper->buildFinalCommand( $wrapper->command );
+ $this->assertEquals( $expected, $output[0], $desc );
+ }
+
+}
--- /dev/null
+<?php
+
+use MediaWiki\Site\MediaWikiPageNameNormalizer;
+
+/**
+ * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
+ *
+ * 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
+ *
+ * @since 1.27
+ *
+ * @group Site
+ * @group medium
+ *
+ * @author Marius Hoch
+ */
+class MediaWikiPageNameNormalizerTest extends MediaWikiUnitTestCase {
+
+ /**
+ * @dataProvider normalizePageTitleProvider
+ */
+ public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
+ MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
+
+ $normalizer = new MediaWikiPageNameNormalizer(
+ new MediaWikiPageNameNormalizerTestMockHttp()
+ );
+
+ $this->assertSame(
+ $expected,
+ $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
+ );
+ }
+
+ public function normalizePageTitleProvider() {
+ // Response are taken from wikidata and kkwiki using the following API request
+ // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
+ return [
+ 'universe (Q1)' => [
+ 'Q1',
+ 'Q1',
+ '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
+ . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
+ . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+ . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
+ ],
+ 'Q404 redirects to Q395' => [
+ 'Q395',
+ 'Q404',
+ '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
+ . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
+ . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+ . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
+ ],
+ 'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
+ 'Д',
+ 'D',
+ '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
+ . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
+ . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
+ . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
+ . '"lastrevid":2373618,"length":3501}}}}'
+ ],
+ 'there is no Q0' => [
+ false,
+ 'Q0',
+ '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
+ . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
+ . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
+ ],
+ 'invalid title' => [
+ false,
+ '{{',
+ '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
+ . '"invalidreason":"The requested page title contains invalid '
+ . 'characters: \"{\".","invalid":""}}}}'
+ ],
+ 'error on get' => [ false, 'ABC', false ]
+ ];
+ }
+
+}
+
+/**
+ * @private
+ * @see Http
+ */
+class MediaWikiPageNameNormalizerTestMockHttp extends Http {
+
+ /**
+ * @var mixed
+ */
+ public static $response;
+
+ public static function get( $url, array $options = [], $caller = __METHOD__ ) {
+ PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
+ PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
+
+ return self::$response;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @covers ZipDirectoryReader
+ * NOTE: this test is more like an integration test than a unit test
+ */
+class ZipDirectoryReaderTest extends MediaWikiUnitTestCase {
+
+ protected $zipDir;
+ protected $entries;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->zipDir = __DIR__ . '/../../../data/zip';
+ }
+
+ function zipCallback( $entry ) {
+ $this->entries[] = $entry;
+ }
+
+ function readZipAssertError( $file, $error, $assertMessage ) {
+ $this->entries = [];
+ $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+ $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
+ }
+
+ function readZipAssertSuccess( $file, $assertMessage ) {
+ $this->entries = [];
+ $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+ $this->assertTrue( $status->isOK(), $assertMessage );
+ }
+
+ public function testEmpty() {
+ $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
+ }
+
+ public function testMultiDisk0() {
+ $this->readZipAssertError( 'split.zip', 'zip-unsupported',
+ 'Split zip error' );
+ }
+
+ public function testNoSignature() {
+ $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
+ 'No signature should give "wrong format" error' );
+ }
+
+ public function testSimple() {
+ $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
+ $this->assertEquals( $this->entries, [ [
+ 'name' => 'Class.class',
+ 'mtime' => '20010115000000',
+ 'size' => 1,
+ ] ] );
+ }
+
+ public function testBadCentralEntrySignature() {
+ $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
+ 'Bad central entry error' );
+ }
+
+ public function testTrailingBytes() {
+ // Due to T40432 this is now zip-wrong-format instead of zip-bad
+ $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
+ 'Trailing bytes error' );
+ }
+
+ public function testWrongCDStart() {
+ $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
+ 'Wrong CD start disk error' );
+ }
+
+ public function testCentralDirectoryGap() {
+ $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
+ 'CD gap error' );
+ }
+
+ public function testCentralDirectoryTruncated() {
+ $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
+ 'CD truncated error (should hit unpack() overrun)' );
+ }
+
+ public function testLooksLikeZip64() {
+ $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
+ 'A file which looks like ZIP64 but isn\'t, should give error' );
+ }
+}