With the introduction of a REST API into MediaWiki core, we're going to want to share parameter validation logic rather than having similar code in both the Action API and the REST API. This abstracts out parameter validation logic as a library. There will be at least two follow-up patches: * One to add calls in the REST API, plus the interface for the REST API to do body validation. Should be reasonably straightforward. * One to adjust the Action API to use this. That'll be much less straightforward, as the Action API needs some MediaWiki-specific types (which the REST API might use too in the future) and needs to override the defaults on some of the library's checks (to maintain back-compat). Bug: T142080 Bug: T223239 Change-Id: I5c0cc3a8d686ace97596df5832c450a6a50f902c Depends-On: Iea05dc439688871c574c639e617765ae88a75ff7
294 lines
7.9 KiB
PHP
294 lines
7.9 KiB
PHP
<?php
|
|
|
|
namespace Wikimedia\ParamValidator\Util;
|
|
|
|
require_once __DIR__ . '/UploadedFileTestBase.php';
|
|
|
|
use RuntimeException;
|
|
use Wikimedia\AtEase\AtEase;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @covers Wikimedia\ParamValidator\Util\UploadedFileStream
|
|
*/
|
|
class UploadedFileStreamTest extends UploadedFileTestBase {
|
|
|
|
/**
|
|
* @expectedException RuntimeException
|
|
* @expectedExceptionMessage Failed to open file:
|
|
*/
|
|
public function testConstruct_doesNotExist() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
unlink( $filename );
|
|
|
|
$this->assertFileNotExists( $filename, 'sanity check' );
|
|
$stream = new UploadedFileStream( $filename );
|
|
}
|
|
|
|
/**
|
|
* @expectedException RuntimeException
|
|
* @expectedExceptionMessage Failed to open file:
|
|
*/
|
|
public function testConstruct_notReadable() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
|
|
chmod( $filename, 0000 );
|
|
$stream = new UploadedFileStream( $filename );
|
|
}
|
|
|
|
public function testCloseOnDestruct() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
$fp = TestingAccessWrapper::newFromObject( $stream )->fp;
|
|
$this->assertSame( 'f', fread( $fp, 1 ), 'sanity check' );
|
|
unset( $stream );
|
|
$this->assertFalse( AtEase::quietCall( 'fread', $fp, 1 ) );
|
|
}
|
|
|
|
public function testToString() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
// Always starts at the start of the stream
|
|
$stream->seek( 3 );
|
|
$this->assertSame( 'foobar', (string)$stream );
|
|
|
|
// No exception when closed
|
|
$stream->close();
|
|
$this->assertSame( '', (string)$stream );
|
|
}
|
|
|
|
public function testToString_Error() {
|
|
if ( !class_exists( \Error::class ) ) {
|
|
$this->markTestSkipped( 'No PHP Error class' );
|
|
}
|
|
|
|
// ... Yeah
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = $this->getMockBuilder( UploadedFileStream::class )
|
|
->setConstructorArgs( [ $filename ] )
|
|
->setMethods( [ 'getContents' ] )
|
|
->getMock();
|
|
$stream->method( 'getContents' )->willReturnCallback( function () {
|
|
throw new \Error( 'Bogus' );
|
|
} );
|
|
$this->assertSame( '', (string)$stream );
|
|
}
|
|
|
|
public function testClose() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
$stream->close();
|
|
|
|
// Second call doesn't error
|
|
$stream->close();
|
|
}
|
|
|
|
public function testDetach() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
// We got the file descriptor
|
|
$fp = $stream->detach();
|
|
$this->assertNotNull( $fp );
|
|
$this->assertSame( 'f', fread( $fp, 1 ) );
|
|
|
|
// Stream operations now fail.
|
|
try {
|
|
$stream->seek( 0 );
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
|
|
// Stream close doesn't affect the file descriptor
|
|
$stream->close();
|
|
$this->assertSame( 'o', fread( $fp, 1 ) );
|
|
|
|
// Stream destruction doesn't affect the file descriptor
|
|
unset( $stream );
|
|
$this->assertSame( 'o', fread( $fp, 1 ) );
|
|
|
|
// On a closed stream, we don't get a file descriptor
|
|
$stream = new UploadedFileStream( $filename );
|
|
$stream->close();
|
|
$this->assertNull( $stream->detach() );
|
|
}
|
|
|
|
public function testGetSize() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
file_put_contents( $filename, 'foobarbaz' );
|
|
$this->assertSame( 9, $stream->getSize() );
|
|
|
|
// Cached
|
|
file_put_contents( $filename, 'baz' );
|
|
clearstatcache();
|
|
$this->assertSame( 3, stat( $filename )['size'], 'sanity check' );
|
|
$this->assertSame( 9, $stream->getSize() );
|
|
|
|
// No error if closed
|
|
$stream = new UploadedFileStream( $filename );
|
|
$stream->close();
|
|
$this->assertSame( null, $stream->getSize() );
|
|
|
|
// No error even if the fd goes bad
|
|
$stream = new UploadedFileStream( $filename );
|
|
fclose( TestingAccessWrapper::newFromObject( $stream )->fp );
|
|
$this->assertSame( null, $stream->getSize() );
|
|
}
|
|
|
|
public function testSeekTell() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
$stream->seek( 2 );
|
|
$this->assertSame( 2, $stream->tell() );
|
|
$stream->seek( 2, SEEK_CUR );
|
|
$this->assertSame( 4, $stream->tell() );
|
|
$stream->seek( -5, SEEK_END );
|
|
$this->assertSame( 1, $stream->tell() );
|
|
$stream->read( 2 );
|
|
$this->assertSame( 3, $stream->tell() );
|
|
|
|
$stream->close();
|
|
try {
|
|
$stream->seek( 0 );
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
try {
|
|
$stream->tell();
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
}
|
|
|
|
public function testEof() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
$this->assertFalse( $stream->eof() );
|
|
$stream->getContents();
|
|
$this->assertTrue( $stream->eof() );
|
|
$stream->seek( -1, SEEK_END );
|
|
$this->assertFalse( $stream->eof() );
|
|
|
|
// No error if closed
|
|
$stream = new UploadedFileStream( $filename );
|
|
$stream->close();
|
|
$this->assertTrue( $stream->eof() );
|
|
|
|
// No error even if the fd goes bad
|
|
$stream = new UploadedFileStream( $filename );
|
|
fclose( TestingAccessWrapper::newFromObject( $stream )->fp );
|
|
$this->assertInternalType( 'boolean', $stream->eof() );
|
|
}
|
|
|
|
public function testIsFuncs() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
$this->assertTrue( $stream->isSeekable() );
|
|
$this->assertTrue( $stream->isReadable() );
|
|
$this->assertFalse( $stream->isWritable() );
|
|
|
|
$stream->close();
|
|
$this->assertFalse( $stream->isSeekable() );
|
|
$this->assertFalse( $stream->isReadable() );
|
|
$this->assertFalse( $stream->isWritable() );
|
|
}
|
|
|
|
public function testRewind() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
$stream->seek( 2 );
|
|
$this->assertSame( 2, $stream->tell() );
|
|
$stream->rewind();
|
|
$this->assertSame( 0, $stream->tell() );
|
|
|
|
$stream->close();
|
|
try {
|
|
$stream->rewind();
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
}
|
|
|
|
public function testWrite() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
try {
|
|
$stream->write( 'foo' );
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
}
|
|
|
|
public function testRead() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
$this->assertSame( 'foo', $stream->read( 3 ) );
|
|
$this->assertSame( 'bar', $stream->read( 10 ) );
|
|
$this->assertSame( '', $stream->read( 10 ) );
|
|
$stream->rewind();
|
|
$this->assertSame( 'foobar', $stream->read( 10 ) );
|
|
|
|
$stream->close();
|
|
try {
|
|
$stream->read( 1 );
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
}
|
|
|
|
public function testGetContents() {
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$stream = new UploadedFileStream( $filename );
|
|
|
|
$this->assertSame( 'foobar', $stream->getContents() );
|
|
$this->assertSame( '', $stream->getContents() );
|
|
$stream->seek( 3 );
|
|
$this->assertSame( 'bar', $stream->getContents() );
|
|
|
|
$stream->close();
|
|
try {
|
|
$stream->getContents();
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
}
|
|
|
|
public function testGetMetadata() {
|
|
// Whatever
|
|
$filename = $this->makeTemp( __FUNCTION__ );
|
|
$fp = fopen( $filename, 'r' );
|
|
$expect = stream_get_meta_data( $fp );
|
|
fclose( $fp );
|
|
|
|
$stream = new UploadedFileStream( $filename );
|
|
$this->assertSame( $expect, $stream->getMetadata() );
|
|
foreach ( $expect as $k => $v ) {
|
|
$this->assertSame( $v, $stream->getMetadata( $k ) );
|
|
}
|
|
$this->assertNull( $stream->getMetadata( 'bogus' ) );
|
|
|
|
$stream->close();
|
|
try {
|
|
$stream->getMetadata();
|
|
} catch ( \PHPUnit\Framework\AssertionFailedError $ex ) {
|
|
throw $ex;
|
|
} catch ( RuntimeException $ex ) {
|
|
}
|
|
}
|
|
|
|
}
|