summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/includes/utils
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/includes/utils')
-rw-r--r--www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php118
-rw-r--r--www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php252
-rw-r--r--www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php56
-rw-r--r--www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php57
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php98
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php64
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWGrantsTest.php117
-rw-r--r--www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php217
-rw-r--r--www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php173
-rw-r--r--www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php87
10 files changed, 1239 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php b/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php
new file mode 100644
index 00000000..cf45f9fd
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/AvroValidatorTest.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Tests for IP validity functions.
+ *
+ * Ported from /t/inc/IP.t by avar.
+ *
+ * @todo Test methods in this call should be split into a method and a
+ * dataprovider.
+ */
+
+/**
+ * @group IP
+ * @covers AvroValidator
+ */
+class AvroValidatorTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function setUp() {
+ if ( !class_exists( 'AvroSchema' ) ) {
+ $this->markTestSkipped( 'Avro is required to run the AvroValidatorTest' );
+ }
+ parent::setUp();
+ }
+
+ public function getErrorsProvider() {
+ $stringSchema = AvroSchema::parse( json_encode( [ 'type' => 'string' ] ) );
+ $stringArraySchema = AvroSchema::parse( json_encode( [
+ 'type' => 'array',
+ 'items' => 'string',
+ ] ) );
+ $recordSchema = AvroSchema::parse( json_encode( [
+ 'type' => 'record',
+ 'name' => 'ut',
+ 'fields' => [
+ [ 'name' => 'id', 'type' => 'int', 'required' => true ],
+ ],
+ ] ) );
+ $enumSchema = AvroSchema::parse( json_encode( [
+ 'type' => 'record',
+ 'name' => 'ut',
+ 'fields' => [
+ [ 'name' => 'count', 'type' => [ 'int', 'null' ] ],
+ ],
+ ] ) );
+
+ return [
+ [
+ 'No errors with a simple string serialization',
+ $stringSchema, 'foobar', [],
+ ],
+
+ [
+ 'Cannot serialize integer into string',
+ $stringSchema, 5, 'Expected string, but recieved integer',
+ ],
+
+ [
+ 'Cannot serialize array into string',
+ $stringSchema, [], 'Expected string, but recieved array',
+ ],
+
+ [
+ 'allows and ignores extra fields',
+ $recordSchema, [ 'id' => 4, 'foo' => 'bar' ], [],
+ ],
+
+ [
+ 'detects missing fields',
+ $recordSchema, [], [ 'id' => 'Missing expected field' ],
+ ],
+
+ [
+ 'handles first element in enum',
+ $enumSchema, [ 'count' => 4 ], [],
+ ],
+
+ [
+ 'handles second element in enum',
+ $enumSchema, [ 'count' => null ], [],
+ ],
+
+ [
+ 'rejects element not in union',
+ $enumSchema, [ 'count' => 'invalid' ], [ 'count' => [
+ 'Expected any one of these to be true',
+ [
+ 'Expected integer, but recieved string',
+ 'Expected null, but recieved string',
+ ]
+ ] ]
+ ],
+ [
+ 'Empty array is accepted',
+ $stringArraySchema, [], []
+ ],
+ [
+ 'correct array element accepted',
+ $stringArraySchema, [ 'fizzbuzz' ], []
+ ],
+ [
+ 'incorrect array element rejected',
+ $stringArraySchema, [ '12', 34 ], [ 'Expected string, but recieved integer' ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider getErrorsProvider
+ */
+ public function testGetErrors( $message, $schema, $datum, $expected ) {
+ $this->assertEquals(
+ $expected,
+ AvroValidator::getErrors( $schema, $datum ),
+ $message
+ );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php
new file mode 100644
index 00000000..52b14339
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/BatchRowUpdateTest.php
@@ -0,0 +1,252 @@
+<?php
+
+/**
+ * Tests for BatchRowUpdate and its components
+ *
+ * @group db
+ *
+ * @covers BatchRowUpdate
+ * @covers BatchRowIterator
+ * @covers BatchRowWriter
+ */
+class BatchRowUpdateTest extends MediaWikiTestCase {
+
+ public function testWriterBasicFunctionality() {
+ $db = $this->mockDb( [ 'update' ] );
+ $writer = new BatchRowWriter( $db, 'echo_event' );
+
+ $updates = [
+ self::mockUpdate( [ 'something' => 'changed' ] ),
+ self::mockUpdate( [ 'otherthing' => 'changed' ] ),
+ self::mockUpdate( [ 'and' => 'something', 'else' => 'changed' ] ),
+ ];
+
+ $db->expects( $this->exactly( count( $updates ) ) )
+ ->method( 'update' );
+
+ $writer->write( $updates );
+ }
+
+ protected static function mockUpdate( array $changes ) {
+ static $i = 0;
+ return [
+ 'primaryKey' => [ 'event_id' => $i++ ],
+ 'changes' => $changes,
+ ];
+ }
+
+ public function testReaderBasicIterate() {
+ $batchSize = 2;
+ $response = $this->genSelectResult( $batchSize, /*numRows*/ 5, function () {
+ static $i = 0;
+ return [ 'id_field' => ++$i ];
+ } );
+ $db = $this->mockDbConsecutiveSelect( $response );
+ $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize );
+
+ $pos = 0;
+ foreach ( $reader as $rows ) {
+ $this->assertEquals( $response[$pos], $rows, "Testing row in position $pos" );
+ $pos++;
+ }
+ // -1 is because the final array() marks the end and isnt included
+ $this->assertEquals( count( $response ) - 1, $pos );
+ }
+
+ public static function provider_readerGetPrimaryKey() {
+ $row = [
+ 'id_field' => 42,
+ 'some_col' => 'dvorak',
+ 'other_col' => 'samurai',
+ ];
+ return [
+
+ [
+ 'Must return single column pk when requested',
+ [ 'id_field' => 42 ],
+ $row
+ ],
+
+ [
+ 'Must return multiple column pks when requested',
+ [ 'id_field' => 42, 'other_col' => 'samurai' ],
+ $row
+ ],
+
+ ];
+ }
+
+ /**
+ * @dataProvider provider_readerGetPrimaryKey
+ */
+ public function testReaderGetPrimaryKey( $message, array $expected, array $row ) {
+ $reader = new BatchRowIterator( $this->mockDb(), 'some_table', array_keys( $expected ), 8675309 );
+ $this->assertEquals( $expected, $reader->extractPrimaryKeys( (object)$row ), $message );
+ }
+
+ public static function provider_readerSetFetchColumns() {
+ return [
+
+ [
+ 'Must merge primary keys into select conditions',
+ // Expected column select
+ [ 'foo', 'bar' ],
+ // primary keys
+ [ 'foo' ],
+ // setFetchColumn
+ [ 'bar' ]
+ ],
+
+ [
+ 'Must not merge primary keys into the all columns selector',
+ // Expected column select
+ [ '*' ],
+ // primary keys
+ [ 'foo' ],
+ // setFetchColumn
+ [ '*' ],
+ ],
+
+ [
+ 'Must not duplicate primary keys into column selector',
+ // Expected column select.
+ // TODO: figure out how to only assert the array_values portion and not the keys
+ [ 0 => 'foo', 1 => 'bar', 3 => 'baz' ],
+ // primary keys
+ [ 'foo', 'bar', ],
+ // setFetchColumn
+ [ 'bar', 'baz' ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provider_readerSetFetchColumns
+ */
+ public function testReaderSetFetchColumns(
+ $message, array $columns, array $primaryKeys, array $fetchColumns
+ ) {
+ $db = $this->mockDb( [ 'select' ] );
+ $db->expects( $this->once() )
+ ->method( 'select' )
+ // only testing second parameter of Database::select
+ ->with( 'some_table', $columns )
+ ->will( $this->returnValue( new ArrayIterator( [] ) ) );
+
+ $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, 22 );
+ $reader->setFetchColumns( $fetchColumns );
+ // triggers first database select
+ $reader->rewind();
+ }
+
+ public static function provider_readerSelectConditions() {
+ return [
+
+ [
+ "With single primary key must generate id > 'value'",
+ // Expected second iteration
+ [ "( id_field > '3' )" ],
+ // Primary key(s)
+ 'id_field',
+ ],
+
+ [
+ 'With multiple primary keys the first conditions ' .
+ 'must use >= and the final condition must use >',
+ // Expected second iteration
+ [ "( id_field = '3' AND foo > '103' ) OR ( id_field > '3' )" ],
+ // Primary key(s)
+ [ 'id_field', 'foo' ],
+ ],
+
+ ];
+ }
+
+ /**
+ * Slightly hackish to use reflection, but asserting different parameters
+ * to consecutive calls of Database::select in phpunit is error prone
+ *
+ * @dataProvider provider_readerSelectConditions
+ */
+ public function testReaderSelectConditionsMultiplePrimaryKeys(
+ $message, $expectedSecondIteration, $primaryKeys, $batchSize = 3
+ ) {
+ $results = $this->genSelectResult( $batchSize, $batchSize * 3, function () {
+ static $i = 0, $j = 100, $k = 1000;
+ return [ 'id_field' => ++$i, 'foo' => ++$j, 'bar' => ++$k ];
+ } );
+ $db = $this->mockDbConsecutiveSelect( $results );
+
+ $conditions = [ 'bar' => 42, 'baz' => 'hai' ];
+ $reader = new BatchRowIterator( $db, 'some_table', $primaryKeys, $batchSize );
+ $reader->addConditions( $conditions );
+
+ $buildConditions = new ReflectionMethod( $reader, 'buildConditions' );
+ $buildConditions->setAccessible( true );
+
+ // On first iteration only the passed conditions must be used
+ $this->assertEquals( $conditions, $buildConditions->invoke( $reader ),
+ 'First iteration must return only the conditions passed in addConditions' );
+ $reader->rewind();
+
+ // Second iteration must use the maximum primary key of last set
+ $this->assertEquals(
+ $conditions + $expectedSecondIteration,
+ $buildConditions->invoke( $reader ),
+ $message
+ );
+ }
+
+ protected function mockDbConsecutiveSelect( array $retvals ) {
+ $db = $this->mockDb( [ 'select', 'addQuotes' ] );
+ $db->expects( $this->any() )
+ ->method( 'select' )
+ ->will( $this->consecutivelyReturnFromSelect( $retvals ) );
+ $db->expects( $this->any() )
+ ->method( 'addQuotes' )
+ ->will( $this->returnCallback( function ( $value ) {
+ return "'$value'"; // not real quoting: doesn't matter in test
+ } ) );
+
+ return $db;
+ }
+
+ protected function consecutivelyReturnFromSelect( array $results ) {
+ $retvals = [];
+ foreach ( $results as $rows ) {
+ // The Database::select method returns iterators, so we do too.
+ $retvals[] = $this->returnValue( new ArrayIterator( $rows ) );
+ }
+
+ return call_user_func_array( [ $this, 'onConsecutiveCalls' ], $retvals );
+ }
+
+ protected function genSelectResult( $batchSize, $numRows, $rowGenerator ) {
+ $res = [];
+ for ( $i = 0; $i < $numRows; $i += $batchSize ) {
+ $rows = [];
+ for ( $j = 0; $j < $batchSize && $i + $j < $numRows; $j++ ) {
+ $rows [] = (object)call_user_func( $rowGenerator );
+ }
+ $res[] = $rows;
+ }
+ $res[] = []; // termination condition requires empty result for last row
+ return $res;
+ }
+
+ protected function mockDb( $methods = [] ) {
+ // @TODO: mock from Database
+ // FIXME: the constructor normally sets mAtomicLevels and mSrvCache
+ $databaseMysql = $this->getMockBuilder( Wikimedia\Rdbms\DatabaseMysqli::class )
+ ->disableOriginalConstructor()
+ ->setMethods( array_merge( [ 'isOpen', 'getApproximateLagStatus' ], $methods ) )
+ ->getMock();
+ $databaseMysql->expects( $this->any() )
+ ->method( 'isOpen' )
+ ->will( $this->returnValue( true ) );
+ $databaseMysql->expects( $this->any() )
+ ->method( 'getApproximateLagStatus' )
+ ->will( $this->returnValue( [ 'lag' => 0, 'since' => 0 ] ) );
+ return $databaseMysql;
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php b/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php
new file mode 100644
index 00000000..9e5163f9
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/ClassCollectorTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @covers ClassCollector
+ */
+class ClassCollectorTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public static function provideCases() {
+ return [
+ [
+ "class Foo {}",
+ [ 'Foo' ],
+ ],
+ [
+ "namespace Example;\nclass Foo {}\nclass Bar {}",
+ [ 'Example\Foo', 'Example\Bar' ],
+ ],
+ [
+ "class_alias( 'Foo', 'Bar' );",
+ [ 'Bar' ],
+ ],
+ [
+ "namespace Example;\nclass Foo {}\nclass_alias( 'Example\Foo', 'Foo' );",
+ [ 'Example\Foo', 'Foo' ],
+ ],
+ [
+ "namespace Example;\nclass Foo {}\nclass_alias( 'Example\Foo', 'Bar' );",
+ [ 'Example\Foo', 'Bar' ],
+ ],
+ [
+ "class_alias( Foo::class, 'Bar' );",
+ [ 'Bar' ],
+ ],
+ [
+ // Namespaced class is not currently supported. Must use namespace declaration
+ // earlier in the file.
+ "class_alias( Example\Foo::class, 'Bar' );",
+ [],
+ ],
+ [
+ "namespace Example;\nclass Foo {}\nclass_alias( Foo::class, 'Bar' );",
+ [ 'Example\Foo', 'Bar' ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideCases
+ */
+ public function testGetClasses( $code, array $classes, $message = null ) {
+ $cc = new ClassCollector();
+ $this->assertEquals( $classes, $cc->getClasses( "<?php\n$code" ), $message );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php b/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php
new file mode 100644
index 00000000..316d9f42
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/FileContentsHasherTest.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @covers FileContentsHasherTest
+ */
+class FileContentsHasherTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function provideSingleFile() {
+ return array_map( function ( $file ) {
+ return [ $file, file_get_contents( $file ) ];
+ }, glob( __DIR__ . '/../../data/filecontentshasher/*.*' ) );
+ }
+
+ public function provideMultipleFiles() {
+ return [
+ [ $this->provideSingleFile() ]
+ ];
+ }
+
+ /**
+ * @covers FileContentsHasher::getFileContentsHash
+ * @covers FileContentsHasher::getFileContentsHashInternal
+ * @dataProvider provideSingleFile
+ */
+ public function testSingleFileHash( $fileName, $contents ) {
+ foreach ( [ 'md4', 'md5' ] as $algo ) {
+ $expectedHash = hash( $algo, $contents );
+ $actualHash = FileContentsHasher::getFileContentsHash( $fileName, $algo );
+ $this->assertEquals( $expectedHash, $actualHash );
+ $actualHashRepeat = FileContentsHasher::getFileContentsHash( $fileName, $algo );
+ $this->assertEquals( $expectedHash, $actualHashRepeat );
+ }
+ }
+
+ /**
+ * @covers FileContentsHasher::getFileContentsHash
+ * @covers FileContentsHasher::getFileContentsHashInternal
+ * @dataProvider provideMultipleFiles
+ */
+ public function testMultipleFileHash( $files ) {
+ $fileNames = [];
+ $hashes = [];
+ foreach ( $files as $fileInfo ) {
+ list( $fileName, $contents ) = $fileInfo;
+ $fileNames[] = $fileName;
+ $hashes[] = md5( $contents );
+ }
+
+ $expectedHash = md5( implode( '', $hashes ) );
+ $actualHash = FileContentsHasher::getFileContentsHash( $fileNames, 'md5' );
+ $this->assertEquals( $expectedHash, $actualHash );
+ $actualHashRepeat = FileContentsHasher::getFileContentsHash( $fileNames, 'md5' );
+ $this->assertEquals( $expectedHash, $actualHashRepeat );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php b/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php
new file mode 100644
index 00000000..05a33c5a
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/MWCryptHKDFTest.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @group HKDF
+ * @covers CryptHKDF
+ * @covers MWCryptHKDF
+ */
+class MWCryptHKDFTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgSecretKey', '5bf1945342e67799cb50704a7fa19ac6' );
+ }
+
+ /**
+ * Test basic usage works
+ */
+ public function testGenerate() {
+ $a = MWCryptHKDF::generateHex( 64 );
+ $b = MWCryptHKDF::generateHex( 64 );
+
+ $this->assertTrue( strlen( $a ) == 64, "MWCryptHKDF produced fewer bytes than expected" );
+ $this->assertTrue( strlen( $b ) == 64, "MWCryptHKDF produced fewer bytes than expected" );
+ $this->assertFalse( $a == $b, "Two runs of MWCryptHKDF produced the same result." );
+ }
+
+ /**
+ * @dataProvider providerRfc5869
+ */
+ public function testRfc5869( $hash, $ikm, $salt, $info, $L, $prk, $okm ) {
+ $ikm = hex2bin( $ikm );
+ $salt = hex2bin( $salt );
+ $info = hex2bin( $info );
+ $okm = hex2bin( $okm );
+ $result = MWCryptHKDF::HKDF( $hash, $ikm, $salt, $info, $L );
+ $this->assertEquals( $okm, $result );
+ }
+
+ /**
+ * Test vectors from Appendix A on https://tools.ietf.org/html/rfc5869
+ */
+ public static function providerRfc5869() {
+ // phpcs:disable Generic.Files.LineLength
+ return [
+ // A.1
+ [
+ 'sha256',
+ '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', // ikm
+ '000102030405060708090a0b0c', // salt
+ 'f0f1f2f3f4f5f6f7f8f9', // context
+ 42, // bytes
+ '077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5', // prk
+ '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865' // okm
+ ],
+ // A.2
+ [
+ 'sha256',
+ '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f',
+ '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf',
+ 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff',
+ 82,
+ '06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244',
+ 'b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87'
+ ],
+ // A.3
+ [
+ 'sha256',
+ '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', // ikm
+ '', // salt
+ '', // context
+ 42, // bytes
+ '19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04', // prk
+ '8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8' // okm
+ ],
+ // A.4
+ [
+ 'sha1',
+ '0b0b0b0b0b0b0b0b0b0b0b', // ikm
+ '000102030405060708090a0b0c', // salt
+ 'f0f1f2f3f4f5f6f7f8f9', // context
+ 42, // bytes
+ '9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243', // prk
+ '085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896' // okm
+ ],
+ // A.5
+ [
+ 'sha1',
+ '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', // ikm
+ '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', // salt
+ 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', // context
+ 82, // bytes
+ '8adae09a2a307059478d309b26c4115a224cfaf6', // prk
+ '0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4' // okm
+ ],
+ ];
+ // phpcs:enable
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php b/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php
new file mode 100644
index 00000000..94705bff
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/MWCryptHashTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @group Hash
+ *
+ * @covers MWCryptHash
+ */
+class MWCryptHashTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public function testHashLength() {
+ if ( MWCryptHash::hashAlgo() !== 'whirlpool' ) {
+ $this->markTestSkipped( 'Hash algorithm isn\'t whirlpool' );
+ }
+
+ $this->assertEquals( 64, MWCryptHash::hashLength(), 'Raw hash length' );
+ $this->assertEquals( 128, MWCryptHash::hashLength( false ), 'Hex hash length' );
+ }
+
+ public function testHash() {
+ if ( MWCryptHash::hashAlgo() !== 'whirlpool' ) {
+ $this->markTestSkipped( 'Hash algorithm isn\'t whirlpool' );
+ }
+
+ $data = 'foobar';
+ // phpcs:ignore Generic.Files.LineLength
+ $hash = '9923afaec3a86f865bb231a588f453f84e8151a2deb4109aebc6de4284be5bebcff4fab82a7e51d920237340a043736e9d13bab196006dcca0fe65314d68eab9';
+
+ $this->assertEquals(
+ hex2bin( $hash ),
+ MWCryptHash::hash( $data ),
+ 'Raw hash'
+ );
+ $this->assertEquals(
+ $hash,
+ MWCryptHash::hash( $data, false ),
+ 'Hex hash'
+ );
+ }
+
+ public function testHmac() {
+ if ( MWCryptHash::hashAlgo() !== 'whirlpool' ) {
+ $this->markTestSkipped( 'Hash algorithm isn\'t whirlpool' );
+ }
+
+ $data = 'foobar';
+ $key = 'secret';
+ // phpcs:ignore Generic.Files.LineLength
+ $hash = 'ddc94177b2020e55ce2049199fd9cc6327f416ff6dc621cc34cb43d9bec61d73372b4790c0e24957f565ecaf2d42821e6303619093e99cbe14a3b9250bda5f81';
+
+ $this->assertEquals(
+ hex2bin( $hash ),
+ MWCryptHash::hmac( $data, $key ),
+ 'Raw hmac'
+ );
+ $this->assertEquals(
+ $hash,
+ MWCryptHash::hmac( $data, $key, false ),
+ 'Hex hmac'
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/MWGrantsTest.php b/www/wiki/tests/phpunit/includes/utils/MWGrantsTest.php
new file mode 100644
index 00000000..eae9c15d
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/MWGrantsTest.php
@@ -0,0 +1,117 @@
+<?php
+class MWGrantsTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgGrantPermissions' => [
+ 'hidden1' => [ 'read' => true, 'autoconfirmed' => false ],
+ 'hidden2' => [ 'autoconfirmed' => true ],
+ 'normal' => [ 'edit' => true ],
+ 'normal2' => [ 'edit' => true, 'create' => true ],
+ 'admin' => [ 'protect' => true, 'delete' => true ],
+ ],
+ 'wgGrantPermissionGroups' => [
+ 'hidden1' => 'hidden',
+ 'hidden2' => 'hidden',
+ 'normal' => 'normal-group',
+ 'admin' => 'admin',
+ ],
+ ] );
+ }
+
+ /**
+ * @covers MWGrants::getValidGrants
+ */
+ public function testGetValidGrants() {
+ $this->assertSame(
+ [ 'hidden1', 'hidden2', 'normal', 'normal2', 'admin' ],
+ MWGrants::getValidGrants()
+ );
+ }
+
+ /**
+ * @covers MWGrants::getRightsByGrant
+ */
+ public function testGetRightsByGrant() {
+ $this->assertSame(
+ [
+ 'hidden1' => [ 'read' ],
+ 'hidden2' => [ 'autoconfirmed' ],
+ 'normal' => [ 'edit' ],
+ 'normal2' => [ 'edit', 'create' ],
+ 'admin' => [ 'protect', 'delete' ],
+ ],
+ MWGrants::getRightsByGrant()
+ );
+ }
+
+ /**
+ * @dataProvider provideGetGrantRights
+ * @covers MWGrants::getGrantRights
+ * @param array|string $grants
+ * @param array $rights
+ */
+ public function testGetGrantRights( $grants, $rights ) {
+ $this->assertSame( $rights, MWGrants::getGrantRights( $grants ) );
+ }
+
+ public static function provideGetGrantRights() {
+ return [
+ [ 'hidden1', [ 'read' ] ],
+ [ [ 'hidden1', 'hidden2', 'hidden3' ], [ 'read', 'autoconfirmed' ] ],
+ [ [ 'normal1', 'normal2' ], [ 'edit', 'create' ] ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGrantsAreValid
+ * @covers MWGrants::grantsAreValid
+ * @param array $grants
+ * @param bool $valid
+ */
+ public function testGrantsAreValid( $grants, $valid ) {
+ $this->assertSame( $valid, MWGrants::grantsAreValid( $grants ) );
+ }
+
+ public static function provideGrantsAreValid() {
+ return [
+ [ [ 'hidden1', 'hidden2' ], true ],
+ [ [ 'hidden1', 'hidden3' ], false ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetGrantGroups
+ * @covers MWGrants::getGrantGroups
+ * @param array|null $grants
+ * @param array $expect
+ */
+ public function testGetGrantGroups( $grants, $expect ) {
+ $this->assertSame( $expect, MWGrants::getGrantGroups( $grants ) );
+ }
+
+ public static function provideGetGrantGroups() {
+ return [
+ [ null, [
+ 'hidden' => [ 'hidden1', 'hidden2' ],
+ 'normal-group' => [ 'normal' ],
+ 'other' => [ 'normal2' ],
+ 'admin' => [ 'admin' ],
+ ] ],
+ [ [ 'hidden1', 'normal' ], [
+ 'hidden' => [ 'hidden1' ],
+ 'normal-group' => [ 'normal' ],
+ ] ],
+ ];
+ }
+
+ /**
+ * @covers MWGrants::getHiddenGrants
+ */
+ public function testGetHiddenGrants() {
+ $this->assertSame( [ 'hidden1', 'hidden2' ], MWGrants::getHiddenGrants() );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php b/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php
new file mode 100644
index 00000000..abdfbb14
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/MWRestrictionsTest.php
@@ -0,0 +1,217 @@
+<?php
+class MWRestrictionsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ protected static $restrictionsForChecks;
+
+ public static function setUpBeforeClass() {
+ self::$restrictionsForChecks = MWRestrictions::newFromArray( [
+ 'IPAddresses' => [
+ '10.0.0.0/8',
+ '172.16.0.0/12',
+ '2001:db8::/33',
+ ]
+ ] );
+ }
+
+ /**
+ * @covers MWRestrictions::newDefault
+ * @covers MWRestrictions::__construct
+ */
+ public function testNewDefault() {
+ $ret = MWRestrictions::newDefault();
+ $this->assertInstanceOf( MWRestrictions::class, $ret );
+ $this->assertSame(
+ '{"IPAddresses":["0.0.0.0/0","::/0"]}',
+ $ret->toJson()
+ );
+ }
+
+ /**
+ * @covers MWRestrictions::newFromArray
+ * @covers MWRestrictions::__construct
+ * @covers MWRestrictions::loadFromArray
+ * @covers MWRestrictions::toArray
+ * @dataProvider provideArray
+ * @param array $data
+ * @param bool|InvalidArgumentException $expect True if the call succeeds,
+ * otherwise the exception that should be thrown.
+ */
+ public function testArray( $data, $expect ) {
+ if ( $expect === true ) {
+ $ret = MWRestrictions::newFromArray( $data );
+ $this->assertInstanceOf( MWRestrictions::class, $ret );
+ $this->assertSame( $data, $ret->toArray() );
+ } else {
+ try {
+ MWRestrictions::newFromArray( $data );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( InvalidArgumentException $ex ) {
+ $this->assertEquals( $expect, $ex );
+ }
+ }
+ }
+
+ public static function provideArray() {
+ return [
+ [ [ 'IPAddresses' => [] ], true ],
+ [ [ 'IPAddresses' => [ '127.0.0.1/32' ] ], true ],
+ [
+ [ 'IPAddresses' => [ '256.0.0.1/32' ] ],
+ new InvalidArgumentException( 'Invalid IP address: 256.0.0.1/32' )
+ ],
+ [
+ [ 'IPAddresses' => '127.0.0.1/32' ],
+ new InvalidArgumentException( 'IPAddresses is not an array' )
+ ],
+ [
+ [],
+ new InvalidArgumentException( 'Array is missing required keys: IPAddresses' )
+ ],
+ [
+ [ 'foo' => 'bar', 'bar' => 42 ],
+ new InvalidArgumentException( 'Array contains invalid keys: foo, bar' )
+ ],
+ ];
+ }
+
+ /**
+ * @covers MWRestrictions::newFromJson
+ * @covers MWRestrictions::__construct
+ * @covers MWRestrictions::loadFromArray
+ * @covers MWRestrictions::toJson
+ * @covers MWRestrictions::__toString
+ * @dataProvider provideJson
+ * @param string $json
+ * @param array|InvalidArgumentException $expect
+ */
+ public function testJson( $json, $expect ) {
+ if ( is_array( $expect ) ) {
+ $ret = MWRestrictions::newFromJson( $json );
+ $this->assertInstanceOf( MWRestrictions::class, $ret );
+ $this->assertSame( $expect, $ret->toArray() );
+
+ $this->assertSame( $json, $ret->toJson( false ) );
+ $this->assertSame( $json, (string)$ret );
+
+ $this->assertSame(
+ FormatJson::encode( $expect, true, FormatJson::ALL_OK ),
+ $ret->toJson( true )
+ );
+ } else {
+ try {
+ MWRestrictions::newFromJson( $json );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( InvalidArgumentException $ex ) {
+ $this->assertTrue( true );
+ }
+ }
+ }
+
+ public static function provideJson() {
+ return [
+ [
+ '{"IPAddresses":[]}',
+ [ 'IPAddresses' => [] ]
+ ],
+ [
+ '{"IPAddresses":["127.0.0.1/32"]}',
+ [ 'IPAddresses' => [ '127.0.0.1/32' ] ]
+ ],
+ [
+ '{"IPAddresses":["256.0.0.1/32"]}',
+ new InvalidArgumentException( 'Invalid IP address: 256.0.0.1/32' )
+ ],
+ [
+ '{"IPAddresses":"127.0.0.1/32"}',
+ new InvalidArgumentException( 'IPAddresses is not an array' )
+ ],
+ [
+ '{}',
+ new InvalidArgumentException( 'Array is missing required keys: IPAddresses' )
+ ],
+ [
+ '{"foo":"bar","bar":42}',
+ new InvalidArgumentException( 'Array contains invalid keys: foo, bar' )
+ ],
+ [
+ '{"IPAddresses":[]',
+ new InvalidArgumentException( 'Invalid restrictions JSON' )
+ ],
+ [
+ '"IPAddresses"',
+ new InvalidArgumentException( 'Invalid restrictions JSON' )
+ ],
+ ];
+ }
+
+ /**
+ * @covers MWRestrictions::checkIP
+ * @dataProvider provideCheckIP
+ * @param string $ip
+ * @param bool $pass
+ */
+ public function testCheckIP( $ip, $pass ) {
+ $this->assertSame( $pass, self::$restrictionsForChecks->checkIP( $ip ) );
+ }
+
+ public static function provideCheckIP() {
+ return [
+ [ '10.0.0.1', true ],
+ [ '172.16.0.0', true ],
+ [ '192.0.2.1', false ],
+ [ '2001:db8:1::', true ],
+ [ '2001:0db8:0000:0000:0000:0000:0000:0000', true ],
+ [ '2001:0DB8:8000::', false ],
+ ];
+ }
+
+ /**
+ * @covers MWRestrictions::check
+ * @dataProvider provideCheck
+ * @param WebRequest $request
+ * @param Status $expect
+ */
+ public function testCheck( $request, $expect ) {
+ $this->assertEquals( $expect, self::$restrictionsForChecks->check( $request ) );
+ }
+
+ public function provideCheck() {
+ $ret = [];
+
+ $mockBuilder = $this->getMockBuilder( FauxRequest::class )
+ ->setMethods( [ 'getIP' ] );
+
+ foreach ( self::provideCheckIP() as $checkIP ) {
+ $ok = [];
+ $request = $mockBuilder->getMock();
+
+ $request->expects( $this->any() )->method( 'getIP' )
+ ->will( $this->returnValue( $checkIP[0] ) );
+ $ok['ip'] = $checkIP[1];
+
+ /* If we ever add more restrictions, add nested for loops here:
+ * foreach ( self::provideCheckFoo() as $checkFoo ) {
+ * $request->expects( $this->any() )->method( 'getFoo' )
+ * ->will( $this->returnValue( $checkFoo[0] );
+ * $ok['foo'] = $checkFoo[1];
+ *
+ * foreach ( self::provideCheckBar() as $checkBar ) {
+ * $request->expects( $this->any() )->method( 'getBar' )
+ * ->will( $this->returnValue( $checkBar[0] );
+ * $ok['bar'] = $checkBar[1];
+ *
+ * // etc.
+ * }
+ * }
+ */
+
+ $status = Status::newGood();
+ $status->setResult( $ok === array_filter( $ok ), $ok );
+ $ret[] = [ $request, $status ];
+ }
+
+ return $ret;
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php b/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php
new file mode 100644
index 00000000..d335a93a
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/UIDGeneratorTest.php
@@ -0,0 +1,173 @@
+<?php
+
+class UIDGeneratorTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ protected function tearDown() {
+ // Bug: 44850
+ UIDGenerator::unitTestTearDown();
+ parent::tearDown();
+ }
+
+ /**
+ * Test that generated UIDs have the expected properties
+ *
+ * @dataProvider provider_testTimestampedUID
+ * @covers UIDGenerator::newTimestampedUID128
+ * @covers UIDGenerator::newTimestampedUID88
+ */
+ public function testTimestampedUID( $method, $digitlen, $bits, $tbits, $hostbits ) {
+ $id = call_user_func( [ UIDGenerator::class, $method ] );
+ $this->assertEquals( true, ctype_digit( $id ), "UID made of digit characters" );
+ $this->assertLessThanOrEqual( $digitlen, strlen( $id ),
+ "UID has the right number of digits" );
+ $this->assertLessThanOrEqual( $bits, strlen( Wikimedia\base_convert( $id, 10, 2 ) ),
+ "UID has the right number of bits" );
+
+ $ids = [];
+ for ( $i = 0; $i < 300; $i++ ) {
+ $ids[] = call_user_func( [ UIDGenerator::class, $method ] );
+ }
+
+ $lastId = array_shift( $ids );
+
+ $this->assertSame( array_unique( $ids ), $ids, "All generated IDs are unique." );
+
+ foreach ( $ids as $id ) {
+ // Convert string to binary and pad to full length so we can
+ // extract segments
+ $id_bin = Wikimedia\base_convert( $id, 10, 2, $bits );
+ $lastId_bin = Wikimedia\base_convert( $lastId, 10, 2, $bits );
+
+ $timestamp_bin = substr( $id_bin, 0, $tbits );
+ $last_timestamp_bin = substr( $lastId_bin, 0, $tbits );
+
+ $this->assertGreaterThanOrEqual(
+ $last_timestamp_bin,
+ $timestamp_bin,
+ "timestamp ($timestamp_bin) of current ID ($id_bin) >= timestamp ($last_timestamp_bin) " .
+ "of prior one ($lastId_bin)" );
+
+ $hostbits_bin = substr( $id_bin, -$hostbits );
+ $last_hostbits_bin = substr( $lastId_bin, -$hostbits );
+
+ if ( $hostbits ) {
+ $this->assertEquals(
+ $hostbits_bin,
+ $last_hostbits_bin,
+ "Host ID ($hostbits_bin) of current ID ($id_bin) is same as host ID ($last_hostbits_bin) " .
+ "of prior one ($lastId_bin)." );
+ }
+
+ $lastId = $id;
+ }
+ }
+
+ /**
+ * array( method, length, bits, hostbits )
+ * NOTE: When adding a new method name here please update the covers tags for the tests!
+ */
+ public static function provider_testTimestampedUID() {
+ return [
+ [ 'newTimestampedUID128', 39, 128, 46, 48 ],
+ [ 'newTimestampedUID128', 39, 128, 46, 48 ],
+ [ 'newTimestampedUID88', 27, 88, 46, 32 ],
+ ];
+ }
+
+ /**
+ * @covers UIDGenerator::newUUIDv1
+ */
+ public function testUUIDv1() {
+ $ids = [];
+ for ( $i = 0; $i < 100; $i++ ) {
+ $id = UIDGenerator::newUUIDv1();
+ $this->assertEquals( true,
+ preg_match( '!^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$!', $id ),
+ "UID $id has the right format" );
+ $ids[] = $id;
+
+ $id = UIDGenerator::newRawUUIDv1();
+ $this->assertEquals( true,
+ preg_match( '!^[0-9a-f]{12}1[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ),
+ "UID $id has the right format" );
+
+ $id = UIDGenerator::newRawUUIDv1();
+ $this->assertEquals( true,
+ preg_match( '!^[0-9a-f]{12}1[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ),
+ "UID $id has the right format" );
+ }
+
+ $this->assertEquals( array_unique( $ids ), $ids, "All generated IDs are unique." );
+ }
+
+ /**
+ * @covers UIDGenerator::newUUIDv4
+ */
+ public function testUUIDv4() {
+ $ids = [];
+ for ( $i = 0; $i < 100; $i++ ) {
+ $id = UIDGenerator::newUUIDv4();
+ $ids[] = $id;
+ $this->assertEquals( true,
+ preg_match( '!^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$!', $id ),
+ "UID $id has the right format" );
+ }
+
+ $this->assertEquals( array_unique( $ids ), $ids, 'All generated IDs are unique.' );
+ }
+
+ /**
+ * @covers UIDGenerator::newRawUUIDv4
+ */
+ public function testRawUUIDv4() {
+ for ( $i = 0; $i < 100; $i++ ) {
+ $id = UIDGenerator::newRawUUIDv4();
+ $this->assertEquals( true,
+ preg_match( '!^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ),
+ "UID $id has the right format" );
+ }
+ }
+
+ /**
+ * @covers UIDGenerator::newRawUUIDv4
+ */
+ public function testRawUUIDv4QuickRand() {
+ for ( $i = 0; $i < 100; $i++ ) {
+ $id = UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND );
+ $this->assertEquals( true,
+ preg_match( '!^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ),
+ "UID $id has the right format" );
+ }
+ }
+
+ /**
+ * @covers UIDGenerator::newSequentialPerNodeID
+ */
+ public function testNewSequentialID() {
+ $id1 = UIDGenerator::newSequentialPerNodeID( 'test', 32 );
+ $id2 = UIDGenerator::newSequentialPerNodeID( 'test', 32 );
+
+ $this->assertInternalType( 'float', $id1, "ID returned as float" );
+ $this->assertInternalType( 'float', $id2, "ID returned as float" );
+ $this->assertGreaterThan( 0, $id1, "ID greater than 1" );
+ $this->assertGreaterThan( $id1, $id2, "IDs increasing in value" );
+ }
+
+ /**
+ * @covers UIDGenerator::newSequentialPerNodeIDs
+ */
+ public function testNewSequentialIDs() {
+ $ids = UIDGenerator::newSequentialPerNodeIDs( 'test', 32, 5 );
+ $lastId = null;
+ foreach ( $ids as $id ) {
+ $this->assertInternalType( 'float', $id, "ID returned as float" );
+ $this->assertGreaterThan( 0, $id, "ID greater than 1" );
+ if ( $lastId ) {
+ $this->assertGreaterThan( $lastId, $id, "IDs increasing in value" );
+ }
+ $lastId = $id;
+ }
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php b/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
new file mode 100644
index 00000000..9f18e5af
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
@@ -0,0 +1,87 @@
+<?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() {
+ $this->readZipAssertError( 'trail.zip', 'zip-bad',
+ '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' );
+ }
+}