Commit cd9394c2 authored by Jeremy Mikola's avatar Jeremy Mikola

Merge pull request #192

parents 3dbbd798 a916f8f6
...@@ -265,9 +265,14 @@ class Bucket ...@@ -265,9 +265,14 @@ class Bucket
* *
* Supported options: * Supported options:
* *
* * _id (mixed): File document identifier. Defaults to a new ObjectId.
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size. * bucket's chunk size.
* *
* * metadata (document): User data for the "metadata" field of the files
* collection document.
*
* @param string $filename Filename * @param string $filename Filename
* @param array $options Upload options * @param array $options Upload options
* @return resource * @return resource
...@@ -322,9 +327,14 @@ class Bucket ...@@ -322,9 +327,14 @@ class Bucket
* *
* Supported options: * Supported options:
* *
* * _id (mixed): File document identifier. Defaults to a new ObjectId.
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size. * bucket's chunk size.
* *
* * metadata (document): User data for the "metadata" field of the files
* collection document.
*
* @param string $filename Filename * @param string $filename Filename
* @param resource $source Readable stream * @param resource $source Readable stream
* @param array $options Stream options * @param array $options Stream options
......
...@@ -17,7 +17,9 @@ use stdClass; ...@@ -17,7 +17,9 @@ use stdClass;
*/ */
class CollectionWrapper class CollectionWrapper
{ {
private $bucketName;
private $chunksCollection; private $chunksCollection;
private $databaseName;
private $checkedIndexes = false; private $checkedIndexes = false;
private $filesCollection; private $filesCollection;
...@@ -33,6 +35,9 @@ class CollectionWrapper ...@@ -33,6 +35,9 @@ class CollectionWrapper
*/ */
public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = []) public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = [])
{ {
$this->databaseName = (string) $databaseName;
$this->bucketName = (string) $bucketName;
$this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions); $this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions);
$this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions); $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions);
} }
...@@ -135,10 +140,14 @@ class CollectionWrapper ...@@ -135,10 +140,14 @@ class CollectionWrapper
return $this->filesCollection->find($filter, $options); return $this->filesCollection->find($filter, $options);
} }
// TODO: Remove this /**
public function getChunksCollection() * Return the bucket name.
*
* @return string
*/
public function getBucketName()
{ {
return $this->chunksCollection; return $this->bucketName;
} }
/** /**
...@@ -160,10 +169,14 @@ class CollectionWrapper ...@@ -160,10 +169,14 @@ class CollectionWrapper
return new IteratorIterator($cursor); return new IteratorIterator($cursor);
} }
// TODO: Remove this /**
public function getFilesCollection() * Return the database name.
*
* @return string
*/
public function getDatabaseName()
{ {
return $this->filesCollection; return $this->databaseName;
} }
/** /**
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace MongoDB\GridFS; namespace MongoDB\GridFS;
use MongoDB\Driver\Exception\Exception; use MongoDB\Exception\InvalidArgumentException;
use MongoDB\GridFS\Exception\CorruptFileException; use MongoDB\GridFS\Exception\CorruptFileException;
use stdClass; use stdClass;
...@@ -17,11 +17,14 @@ class ReadableStream ...@@ -17,11 +17,14 @@ class ReadableStream
private $bufferEmpty; private $bufferEmpty;
private $bufferFresh; private $bufferFresh;
private $bytesSeen = 0; private $bytesSeen = 0;
private $chunkSize;
private $chunkOffset = 0; private $chunkOffset = 0;
private $chunksIterator; private $chunksIterator;
private $file; private $collectionWrapper;
private $firstCheck = true; private $firstCheck = true;
private $id;
private $iteratorEmpty = false; private $iteratorEmpty = false;
private $length;
private $numChunks; private $numChunks;
/** /**
...@@ -33,13 +36,45 @@ class ReadableStream ...@@ -33,13 +36,45 @@ class ReadableStream
*/ */
public function __construct(CollectionWrapper $collectionWrapper, stdClass $file) public function __construct(CollectionWrapper $collectionWrapper, stdClass $file)
{ {
$this->file = $file; if ( ! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) {
throw new CorruptFileException('file.chunkSize is not an integer >= 1');
}
if ( ! isset($file->length) || ! is_integer($file->length) || $file->length < 0) {
throw new CorruptFileException('file.length is not an integer > 0');
}
$this->chunksIterator = $collectionWrapper->getChunksIteratorByFilesId($this->file->_id); if ( ! isset($file->_id) && ! array_key_exists('_id', (array) $file)) {
$this->numChunks = ($file->length >= 0) ? ceil($file->length / $file->chunkSize) : 0; throw new CorruptFileException('file._id does not exist');
}
$this->id = $file->_id;
$this->chunkSize = $file->chunkSize;
$this->length = $file->length;
$this->chunksIterator = $collectionWrapper->getChunksIteratorByFilesId($this->id);
$this->collectionWrapper = $collectionWrapper;
$this->numChunks = ceil($this->length / $this->chunkSize);
$this->initEmptyBuffer(); $this->initEmptyBuffer();
} }
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'bucketName' => $this->collectionWrapper->getBucketName(),
'databaseName' => $this->collectionWrapper->getDatabaseName(),
'id' => $this->id,
'chunkSize' => $this->chunkSize,
'length' => $this->length,
];
}
public function close() public function close()
{ {
fclose($this->buffer); fclose($this->buffer);
...@@ -53,9 +88,18 @@ class ReadableStream ...@@ -53,9 +88,18 @@ class ReadableStream
* *
* @param integer $numBytes Number of bytes to read * @param integer $numBytes Number of bytes to read
* @return string * @return string
* @throws InvalidArgumentException if $numBytes is negative
*/ */
public function downloadNumBytes($numBytes) public function downloadNumBytes($numBytes)
{ {
if ($numBytes < 0) {
throw new InvalidArgumentException(sprintf('$numBytes must be >= zero; given: %d', $numBytes));
}
if ($numBytes == 0) {
return '';
}
if ($this->bufferFresh) { if ($this->bufferFresh) {
rewind($this->buffer); rewind($this->buffer);
$this->bufferFresh = false; $this->bufferFresh = false;
...@@ -77,7 +121,7 @@ class ReadableStream ...@@ -77,7 +121,7 @@ class ReadableStream
$output .= substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft); $output .= substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft);
} }
if ( ! $this->iteratorEmpty && $this->file->length > 0 && $bytesLeft < strlen($this->chunksIterator->current()->data->getData())) { if ( ! $this->iteratorEmpty && $this->length > 0 && $bytesLeft < strlen($this->chunksIterator->current()->data->getData())) {
fwrite($this->buffer, substr($this->chunksIterator->current()->data->getData(), $bytesLeft)); fwrite($this->buffer, substr($this->chunksIterator->current()->data->getData(), $bytesLeft));
$this->bufferEmpty = false; $this->bufferEmpty = false;
} }
...@@ -103,19 +147,24 @@ class ReadableStream ...@@ -103,19 +147,24 @@ class ReadableStream
} }
} }
public function getFile() /**
{ * Return the stream's ID (i.e. file document identifier).
return $this->file; *
} * @return integer
*/
public function getId() public function getId()
{ {
return $this->file->_id; return $this->id;
} }
/**
* Return the stream's size in bytes.
*
* @return integer
*/
public function getSize() public function getSize()
{ {
return $this->file->length; return $this->length;
} }
public function isEOF() public function isEOF()
...@@ -149,8 +198,8 @@ class ReadableStream ...@@ -149,8 +198,8 @@ class ReadableStream
$actualChunkSize = strlen($this->chunksIterator->current()->data->getData()); $actualChunkSize = strlen($this->chunksIterator->current()->data->getData());
$expectedChunkSize = ($this->chunkOffset == $this->numChunks - 1) $expectedChunkSize = ($this->chunkOffset == $this->numChunks - 1)
? ($this->file->length - $this->bytesSeen) ? ($this->length - $this->bytesSeen)
: $this->file->chunkSize; : $this->chunkSize;
if ($actualChunkSize != $expectedChunkSize) { if ($actualChunkSize != $expectedChunkSize) {
throw CorruptFileException::unexpectedSize($actualChunkSize, $expectedChunkSize); throw CorruptFileException::unexpectedSize($actualChunkSize, $expectedChunkSize);
......
...@@ -89,6 +89,21 @@ class WritableStream ...@@ -89,6 +89,21 @@ class WritableStream
] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]); ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
} }
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'bucketName' => $this->collectionWrapper->getBucketName(),
'databaseName' => $this->collectionWrapper->getDatabaseName(),
'file' => $this->file,
];
}
/** /**
* Closes an active stream and flushes all buffered data to GridFS. * Closes an active stream and flushes all buffered data to GridFS.
*/ */
...@@ -111,26 +126,23 @@ class WritableStream ...@@ -111,26 +126,23 @@ class WritableStream
$this->isClosed = true; $this->isClosed = true;
} }
public function getChunkSize() /**
{ * Return the stream's ID (i.e. file document identifier).
return $this->chunkSize; *
} * @return integer
*/
public function getFile()
{
return $this->file;
}
public function getId() public function getId()
{ {
return $this->file['_id']; return $this->file['_id'];
} }
public function getLength() /**
{ * Return the stream's size in bytes.
return $this->length; *
} * Note: this value will increase as more data is written to the stream.
*
* @return integer
*/
public function getSize() public function getSize()
{ {
return $this->length; return $this->length;
......
...@@ -77,9 +77,10 @@ function is_first_key_operator($document) ...@@ -77,9 +77,10 @@ function is_first_key_operator($document)
throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
} }
reset($document);
$firstKey = (string) key($document); $firstKey = (string) key($document);
return (isset($firstKey[0]) && $firstKey[0] == '$'); return (isset($firstKey[0]) && $firstKey[0] === '$');
} }
/** /**
......
This diff is collapsed.
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
namespace MongoDB\Tests\GridFS; namespace MongoDB\Tests\GridFS;
use MongoDB\GridFS;
use MongoDB\Collection; use MongoDB\Collection;
use MongoDB\GridFS\Bucket;
use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase; use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase;
/** /**
...@@ -12,49 +12,47 @@ use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase; ...@@ -12,49 +12,47 @@ use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase;
abstract class FunctionalTestCase extends BaseFunctionalTestCase abstract class FunctionalTestCase extends BaseFunctionalTestCase
{ {
protected $bucket; protected $bucket;
protected $collectionWrapper; protected $chunksCollection;
protected $filesCollection;
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
foreach(['fs.files', 'fs.chunks'] as $collection){
$col = new Collection($this->manager, $this->getDatabaseName(), $collection); $this->bucket = new Bucket($this->manager, $this->getDatabaseName());
$col->drop(); $this->bucket->drop();
}
$this->bucket = new \MongoDB\GridFS\Bucket($this->manager, $this->getDatabaseName()); $this->chunksCollection = new Collection($this->manager, $this->getDatabaseName(), 'fs.chunks');
$this->collectionWrapper = $this->bucket->getCollectionWrapper(); $this->filesCollection = new Collection($this->manager, $this->getDatabaseName(), 'fs.files');
} }
public function tearDown() /**
* Asserts that a variable is a stream containing the expected data.
*
* Note: this will seek to the beginning of the stream before reading.
*
* @param string $expectedContents
* @param resource $stream
*/
protected function assertStreamContents($expectedContents, $stream)
{ {
foreach(['fs.files', 'fs.chunks'] as $collection){ $this->assertInternalType('resource', $stream);
$col = new Collection($this->manager, $this->getDatabaseName(), $collection); $this->assertSame('stream', get_resource_type($stream));
$col->drop(); $this->assertEquals($expectedContents, stream_get_contents($stream, -1,.0));
}
if ($this->hasFailed()) {
return;
}
} }
public function provideInsertChunks() /**
* Creates an in-memory stream with the given data.
*
* @param string $data
* @return resource
*/
protected function createStream($data = '')
{ {
$dataVals = []; $stream = fopen('php://temp', 'w+b');
$testArgs[][] = "hello world"; fwrite($stream, $data);
$testArgs[][] = "1234567890"; rewind($stream);
$testArgs[][] = "~!@#$%^&*()_+";
for($j=0; $j<30; $j++){
$randomTest = "";
for($i=0; $i<100; $i++){
$randomTest .= chr(rand(0, 256));
}
$testArgs[][] = $randomTest;
}
$utf8="";
for($i=0; $i<256; $i++){
$utf8 .= chr($i);
}
$testArgs[][]=$utf8;
return $testArgs;
}
return $stream;
}
} }
This diff is collapsed.
<?php
namespace MongoDB\Tests\GridFS;
use MongoDB\BSON\Binary;
use MongoDB\GridFS\CollectionWrapper;
use MongoDB\GridFS\ReadableStream;
/**
* Functional tests for the internal ReadableStream class.
*/
class ReadableStreamFunctionalTest extends FunctionalTestCase
{
private $collectionWrapper;
public function setUp()
{
parent::setUp();
$this->collectionWrapper = new CollectionWrapper($this->manager, $this->getDatabaseName(), 'fs');
$this->filesCollection->insertMany([
['_id' => 'length-0', 'length' => 0, 'chunkSize' => 4],
['_id' => 'length-0-with-empty-chunk', 'length' => 0, 'chunkSize' => 4],
['_id' => 'length-2', 'length' => 2, 'chunkSize' => 4],
['_id' => 'length-8', 'length' => 8, 'chunkSize' => 4],
['_id' => 'length-10', 'length' => 10, 'chunkSize' => 4],
]);
$this->chunksCollection->insertMany([
['_id' => 1, 'files_id' => 'length-0-with-empty-chunk', 'n' => 0, 'data' => new Binary('', Binary::TYPE_GENERIC)],
['_id' => 2, 'files_id' => 'length-2', 'n' => 0, 'data' => new Binary('ab', Binary::TYPE_GENERIC)],
['_id' => 3, 'files_id' => 'length-8', 'n' => 0, 'data' => new Binary('abcd', Binary::TYPE_GENERIC)],
['_id' => 4, 'files_id' => 'length-8', 'n' => 1, 'data' => new Binary('efgh', Binary::TYPE_GENERIC)],
['_id' => 5, 'files_id' => 'length-10', 'n' => 0, 'data' => new Binary('abcd', Binary::TYPE_GENERIC)],
['_id' => 6, 'files_id' => 'length-10', 'n' => 1, 'data' => new Binary('efgh', Binary::TYPE_GENERIC)],
['_id' => 7, 'files_id' => 'length-10', 'n' => 2, 'data' => new Binary('ij', Binary::TYPE_GENERIC)],
]);
}
public function testValidConstructorFileDocument()
{
new ReadableStream($this->collectionWrapper, (object) ['_id' => null, 'chunkSize' => 1, 'length' => 0]);
}
/**
* @expectedException MongoDB\GridFS\Exception\CorruptFileException
* @dataProvider provideInvalidConstructorFileDocuments
*/
public function testConstructorFileDocumentChecks($file)
{
new ReadableStream($this->collectionWrapper, $file);
}
public function provideInvalidConstructorFileDocuments()
{
$options = [];
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = (object) ['_id' => 1, 'chunkSize' => $value, 'length' => 0];
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = (object) ['_id' => 1, 'chunkSize' => 1, 'length' => $value];
}
$options[][] = (object) ['_id' => 1, 'chunkSize' => 0, 'length' => 0];
$options[][] = (object) ['_id' => 1, 'chunkSize' => 1, 'length' => -1];
$options[][] = (object) ['chunkSize' => 1, 'length' => 0];
return $options;
}
/**
* @dataProvider provideFileIdAndExpectedBytes
*/
public function testDownloadNumBytes($fileId, $numBytes, $expectedBytes)
{
$fileDocument = $this->collectionWrapper->findFileById($fileId);
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
$this->assertSame($expectedBytes, $stream->downloadNumBytes($numBytes));
}
public function provideFileIdAndExpectedBytes()
{
return [
['length-0', 0, ''],
['length-0', 2, ''],
['length-0-with-empty-chunk', 0, ''],
['length-0-with-empty-chunk', 2, ''],
['length-2', 0, ''],
['length-2', 2, 'ab'],
['length-2', 4, 'ab'],
['length-8', 0, ''],
['length-8', 2, 'ab'],
['length-8', 4, 'abcd'],
['length-8', 6, 'abcdef'],
['length-8', 8, 'abcdefgh'],
['length-8', 10, 'abcdefgh'],
['length-10', 0, ''],
['length-10', 2, 'ab'],
['length-10', 4, 'abcd'],
['length-10', 6, 'abcdef'],
['length-10', 8, 'abcdefgh'],
['length-10', 10, 'abcdefghij'],
['length-10', 12, 'abcdefghij'],
];
}
/**
* @dataProvider provideFileIdAndExpectedBytes
*/
public function testDownloadNumBytesCalledMultipleTimes($fileId, $numBytes, $expectedBytes)
{
$fileDocument = $this->collectionWrapper->findFileById($fileId);
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
for ($i = 0; $i < $numBytes; $i++) {
$expectedByte = isset($expectedBytes[$i]) ? $expectedBytes[$i] : '';
$this->assertSame($expectedByte, $stream->downloadNumBytes(1));
}
}
/**
* @expectedException MongoDB\GridFS\Exception\CorruptFileException
* @expectedExceptionMessage Chunk not found for index "2"
*/
public function testDownloadNumBytesWithMissingChunk()
{
$this->chunksCollection->deleteOne(['files_id' => 'length-10', 'n' => 2]);
$fileDocument = $this->collectionWrapper->findFileById('length-10');
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
$stream->downloadNumBytes(10);
}
/**
* @expectedException MongoDB\GridFS\Exception\CorruptFileException
* @expectedExceptionMessage Expected chunk to have index "1" but found "2"
*/
public function testDownloadNumBytesWithUnexpectedChunkIndex()
{
$this->chunksCollection->deleteOne(['files_id' => 'length-10', 'n' => 1]);
$fileDocument = $this->collectionWrapper->findFileById('length-10');
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
$stream->downloadNumBytes(10);
}
/**
* @expectedException MongoDB\GridFS\Exception\CorruptFileException
* @expectedExceptionMessage Expected chunk to have size "2" but found "1"
*/
public function testDownloadNumBytesWithUnexpectedChunkSize()
{
$this->chunksCollection->updateOne(
['files_id' => 'length-10', 'n' => 2],
['$set' => ['data' => new Binary('i', Binary::TYPE_GENERIC)]]
);
$fileDocument = $this->collectionWrapper->findFileById('length-10');
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
$stream->downloadNumBytes(10);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
*/
public function testDownloadNumBytesWithNegativeReadSize()
{
$fileDocument = $this->collectionWrapper->findFileById('length-0');
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
$stream->downloadNumBytes(-1);
}
}
This diff is collapsed.
GridFS Tests
============
The YAML and JSON files in this directory are platform-independent tests
meant to exercise a driver's implementation of GridFS.
Converting to JSON
==================
The tests are written in YAML because it is easier for humans to write
and read, and because YAML supports a standard comment format. Each test
is also provided in JSON format because in some languages it is easier
to parse JSON than YAML.
If you modify any test, you should modify the YAML file and then
regenerate the JSON file from it.
One way to convert the files is using an online web page. I used:
http://www.json2yaml.com/
It's advertised as a JSON to YAML converter but it can be used in either direction.
Note: the yaml2json utility from npm is not capable of converting these YAML tests
because it doesn't implement the full YAML spec.
Format
======
Each test file has two top level sections:
1. data
2. tests
The data section defines the initial contents of the files and chunks
collections for all tests in that file.
The tests section defines the tests to be run. The format of the tests
section will vary slightly depending on what tests are being defined.
In general, they will have the following sections:
1. description
2. arrange
3. act
4. assert
The arrange section, if present, defines changes to be made to the
initial contents of the files and chunks collections (as defined by
the data section) before this particular test is run. These changes
are described in the form of write commands that can be sent directly
to MongoDB.
The act section defines what operation (with which arguments) should
be performed.
The assert section defines what should be true at the end of the test.
This includes checking the return value of the operation, as well as
checking the expected contents of the files and chunks collections. The
expected contents of the files and chunks collections are described
in the form of write commands that modify collections named
expected.files and expected.chunks. Before running these commands,
load the initial files and chunks documents into the expected.files
and expected.chunks collections and then run the commands. At that point
you can assert that fs.files and expected.files are the same, and that
expected.chunks and fs.chunks are the same.
For operations that are expected to succeed the assert section contains
a "result" element describing the expected result. For operations
that are expected to fail the assert section contains an "error"
element describing the expected failure.
The "result" element is either the expected result when it is possible to
know the result in advance, or it is the special value "&result"
which means that we expect a result (not a failure) but the actual
value of the result could be anything. The notation "&result" is
modeled after YAML syntax for defining an anchor, and the
result value may be referenced later in the assert section as
"*result".
Another special notation in the assert section is "*actual", which
is used when the value of a field cannot be known in advance of the
test, so the assert logic should accept whatever the actual value
ended up being.
data:
files:
-
_id: { "$oid" : "000000000000000000000001" }
length: 0
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "d41d8cd98f00b204e9800998ecf8427e"
filename: "length-0"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000002" }
length: 0
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "d41d8cd98f00b204e9800998ecf8427e"
filename: "length-0-with-empty-chunk"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000003" }
length: 2
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "c700ed4fdb1d27055aa3faa2c2432283"
filename: "length-2"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000004" }
length: 8
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "dd254cdc958e53abaa67da9f797125f5"
filename: "length-8"
contentType: "application/octet-stream"
aliases: []
metadata: {}
chunks:
- { _id : { "$oid" : "000000000000000000000001" }, files_id : { "$oid" : "000000000000000000000002" }, n : 0, data : { $hex : "" } }
- { _id : { "$oid" : "000000000000000000000002" }, files_id : { "$oid" : "000000000000000000000003" }, n : 0, data : { $hex : "1122" } }
- { _id : { "$oid" : "000000000000000000000003" }, files_id : { "$oid" : "000000000000000000000004" }, n : 0, data : { $hex : "11223344" } }
- { _id : { "$oid" : "000000000000000000000004" }, files_id : { "$oid" : "000000000000000000000004" }, n : 1, data : { $hex : "55667788" } }
tests:
-
description: "Delete when length is 0"
act:
operation: delete
arguments:
id: { "$oid" : "000000000000000000000001" }
assert:
result: void
data:
-
{ delete : "expected.files", deletes : [
{ q : { _id : { "$oid" : "000000000000000000000001" } }, limit : 1 }
] }
-
description: "Delete when length is 0 and there is one extra empty chunk"
act:
operation: delete
arguments:
id: { "$oid" : "000000000000000000000002" }
assert:
result: void
data:
-
{ delete : "expected.files", deletes : [
{ q : { _id : { "$oid" : "000000000000000000000002" } }, limit : 1 }
] }
-
{ delete : "expected.chunks", deletes : [
{ q : { files_id : { "$oid" : "000000000000000000000002" } }, limit : 0 }
] }
-
description: "Delete when length is 8"
act:
operation: delete
arguments:
id: { "$oid" : "000000000000000000000004" }
assert:
result: void
data:
-
{ delete : "expected.files", deletes : [
{ q : { _id : { "$oid" : "000000000000000000000004" } }, limit : 1 }
] }
-
{ delete : "expected.chunks", deletes : [
{ q : { files_id : { "$oid" : "000000000000000000000004" } }, limit : 0 }
] }
-
description: "Delete when files entry does not exist"
act:
operation: delete
arguments:
id: { "$oid" : "000000000000000000000000" }
assert:
error: "FileNotFound"
-
description: "Delete when files entry does not exist and there are orphaned chunks"
arrange:
data:
-
{ delete : "fs.files", deletes : [
{ q : { _id : { "$oid" : "000000000000000000000004" } }, limit : 1 }
] }
act:
operation: delete
arguments:
id: { "$oid" : "000000000000000000000004" }
assert:
error: "FileNotFound"
data:
-
{ delete : "expected.files", deletes : [
{ q : { _id : { "$oid" : "000000000000000000000004" } }, limit : 1 }
] }
-
{ delete : "expected.chunks", deletes : [
{ q : { files_id : { "$oid" : "000000000000000000000004" } }, limit : 0 }
] }
data:
files:
-
_id: { "$oid" : "000000000000000000000001" }
length: 0
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "d41d8cd98f00b204e9800998ecf8427e"
filename: "length-0"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000002" }
length: 0
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "d41d8cd98f00b204e9800998ecf8427e"
filename: "length-0-with-empty-chunk"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000003" }
length: 2
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "c700ed4fdb1d27055aa3faa2c2432283"
filename: "length-2"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000004" }
length: 8
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "dd254cdc958e53abaa67da9f797125f5"
filename: "length-8"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000005" }
length: 10
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "57d83cd477bfb1ccd975ab33d827a92b"
filename: "length-10"
contentType: "application/octet-stream"
aliases: []
metadata: {}
chunks:
- { _id : { "$oid" : "000000000000000000000001" }, files_id : { "$oid" : "000000000000000000000002" }, n : 0, data : { $hex : "" } }
- { _id : { "$oid" : "000000000000000000000002" }, files_id : { "$oid" : "000000000000000000000003" }, n : 0, data : { $hex : "1122" } }
- { _id : { "$oid" : "000000000000000000000003" }, files_id : { "$oid" : "000000000000000000000004" }, n : 0, data : { $hex : "11223344" } }
- { _id : { "$oid" : "000000000000000000000004" }, files_id : { "$oid" : "000000000000000000000004" }, n : 1, data : { $hex : "55667788" } }
- { _id : { "$oid" : "000000000000000000000005" }, files_id : { "$oid" : "000000000000000000000005" }, n : 0, data : { $hex : "11223344" } }
- { _id : { "$oid" : "000000000000000000000006" }, files_id : { "$oid" : "000000000000000000000005" }, n : 1, data : { $hex : "55667788" } }
- { _id : { "$oid" : "000000000000000000000007" }, files_id : { "$oid" : "000000000000000000000005" }, n : 2, data : { $hex : "99aa" } }
tests:
-
description: "Download when length is zero"
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000001" }
options: { }
assert:
result: { $hex : "" }
-
description: "Download when length is zero and there is one empty chunk"
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000002" }
options: { }
assert:
result: { $hex : "" }
-
description: "Download when there is one chunk"
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000003" }
options: { }
assert:
result: { $hex : "1122" }
-
description: "Download when there are two chunks"
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000004" }
options: { }
assert:
result: { $hex : "1122334455667788" }
-
description: "Download when there are three chunks"
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000005" }
options: { }
assert:
result: { $hex : "112233445566778899aa" }
-
description: "Download when files entry does not exist"
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000000" }
options: { }
assert:
error: "FileNotFound"
-
description: "Download when an intermediate chunk is missing"
arrange:
data:
-
{ delete : "fs.chunks", deletes : [
{ q : { files_id : { "$oid" : "000000000000000000000005" }, n : 1 }, limit : 1 }
] }
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000005" }
assert:
error: "ChunkIsMissing"
-
description: "Download when final chunk is missing"
arrange:
data:
-
{ delete : "fs.chunks", deletes : [
{ q : { files_id : { "$oid" : "000000000000000000000005" }, n : 1 }, limit : 1 }
] }
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000005" }
assert:
error: "ChunkIsMissing"
-
description: "Download when an intermediate chunk is the wrong size"
arrange:
data:
-
{ update : "fs.chunks", updates : [
{ q : { files_id : { "$oid" : "000000000000000000000005" }, n : 1 }, u : { $set : { data : { $hex : "556677" } } } },
{ q : { files_id : { "$oid" : "000000000000000000000005" }, n : 2 }, u : { $set : { data : { $hex : "8899aa" } } } }
] }
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000005" }
assert:
error: "ChunkIsWrongSize"
-
description: "Download when final chunk is the wrong size"
arrange:
data:
-
{ update : "fs.chunks", updates : [
{ q : { files_id : { "$oid" : "000000000000000000000005" }, n : 2 }, u : { $set : { data : { $hex : "99" } } } }
] }
act:
operation: download
arguments:
id: { "$oid" : "000000000000000000000005" }
assert:
error: "ChunkIsWrongSize"
data:
files:
-
_id: { "$oid" : "000000000000000000000001" }
length: 1
chunkSize: 4
uploadDate: { "$date" : "1970-01-01T00:00:00.000Z" }
md5: "47ed733b8d10be225eceba344d533586"
filename: "abc"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000002" }
length: 1
chunkSize: 4
uploadDate: { "$date" : "1970-01-02T00:00:00.000Z" }
md5: "b15835f133ff2e27c7cb28117bfae8f4"
filename: "abc"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000003" }
length: 1
chunkSize: 4
uploadDate: { "$date" : "1970-01-03T00:00:00.000Z" }
md5: "eccbc87e4b5ce2fe28308fd9f2a7baf3"
filename: "abc"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000004" }
length: 1
chunkSize: 4
uploadDate: { "$date" : "1970-01-04T00:00:00.000Z" }
md5: "f623e75af30e62bbd73d6df5b50bb7b5"
filename: "abc"
contentType: "application/octet-stream"
aliases: []
metadata: {}
-
_id: { "$oid" : "000000000000000000000005" }
length: 1
chunkSize: 4
uploadDate: { "$date" : "1970-01-05T00:00:00.000Z" }
md5: "4c614360da93c0a041b22e537de151eb"
filename: "abc"
contentType: "application/octet-stream"
aliases: []
metadata: {}
chunks:
- { _id : { "$oid" : "000000000000000000000001" }, files_id : { "$oid" : "000000000000000000000001" }, n : 0, data : { $hex : "11" } }
- { _id : { "$oid" : "000000000000000000000002" }, files_id : { "$oid" : "000000000000000000000002" }, n : 0, data : { $hex : "22" } }
- { _id : { "$oid" : "000000000000000000000003" }, files_id : { "$oid" : "000000000000000000000003" }, n : 0, data : { $hex : "33" } }
- { _id : { "$oid" : "000000000000000000000004" }, files_id : { "$oid" : "000000000000000000000004" }, n : 0, data : { $hex : "44" } }
- { _id : { "$oid" : "000000000000000000000005" }, files_id : { "$oid" : "000000000000000000000005" }, n : 0, data : { $hex : "55" } }
tests:
-
description: "Download_by_name when revision is 0"
act:
operation: download_by_name
arguments:
filename: "abc"
options: { revision : 0 }
assert:
result: { $hex : "11" }
-
description: "Download_by_name when revision is 1"
act:
operation: download_by_name
arguments:
filename: "abc"
options: { revision : 1 }
assert:
result: { $hex : "22" }
-
description: "Download_by_name when revision is -2"
act:
operation: download_by_name
arguments:
filename: "abc"
options: { revision : -2 }
assert:
result: { $hex : "44" }
-
description: "Download_by_name when revision is -1"
act:
operation: download_by_name
arguments:
filename: "abc"
options: { revision : -1 }
assert:
result: { $hex : "55" }
-
description: "Download_by_name when files entry does not exist"
act:
operation: download_by_name
arguments:
filename: "xyz"
assert:
error: "FileNotFound"
-
description: "Download_by_name when revision does not exist"
act:
operation: download_by_name
arguments:
filename: "abc"
options: { revision : 999 }
assert:
error: "RevisionNotFound"
data:
files: []
chunks: []
tests:
-
description: "Upload when length is 0"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "" }
options: { chunkSizeBytes : 4 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 0, chunkSize : 4, uploadDate : "*actual", md5 : "d41d8cd98f00b204e9800998ecf8427e", filename : "filename" }
] }
-
description: "Upload when length is 1"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "11" }
options: { chunkSizeBytes : 4 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 1, chunkSize : 4, uploadDate : "*actual", md5 : "47ed733b8d10be225eceba344d533586", filename : "filename" }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "11" } }
] }
-
description: "Upload when length is 3"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "112233" }
options: { chunkSizeBytes : 4 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 3, chunkSize : 4, uploadDate : "*actual", md5 : "bafae3a174ab91fc70db7a6aa50f4f52", filename : "filename" }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "112233" } }
] }
-
description: "Upload when length is 4"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "11223344" }
options: { chunkSizeBytes : 4 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 4, chunkSize : 4, uploadDate : "*actual", md5 : "7e7c77cff5705d1f7574a25ef6662117", filename : "filename" }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "11223344" } }
] }
-
description: "Upload when length is 5"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "1122334455" }
options: { chunkSizeBytes : 4 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 5, chunkSize : 4, uploadDate : "*actual", md5 : "283d4fea5dded59cf837d3047328f5af", filename : "filename" }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "11223344" } },
{ _id : "*actual", files_id : "*result", n : 1, data : { $hex : "55" } }
] }
-
description: "Upload when length is 8"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "1122334455667788" }
options: { chunkSizeBytes : 4 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 8, chunkSize : 4, uploadDate : "*actual", md5 : "dd254cdc958e53abaa67da9f797125f5", filename : "filename" }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "11223344" } },
{ _id : "*actual", files_id : "*result", n : 1, data : { $hex : "55667788" } }
] }
-
description: "Upload when contentType is provided"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "11" }
options: { chunkSizeBytes : 4, contentType : "image/jpeg" }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 1, chunkSize : 4, uploadDate : "*actual", md5 : "47ed733b8d10be225eceba344d533586", filename : "filename", contentType : "image/jpeg" }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "11" } }
] }
-
description: "Upload when metadata is provided"
act:
operation: upload
arguments:
filename: "filename"
source: { $hex : "11" }
options:
chunkSizeBytes: 4
metadata: { x : 1 }
assert:
result: "&result"
data:
-
{ insert : "expected.files", documents : [
{ _id : "*result", length : 1, chunkSize : 4, uploadDate : "*actual", md5 : "47ed733b8d10be225eceba344d533586", filename : "filename", metadata : { x : 1 } }
] }
-
{ insert : "expected.chunks", documents : [
{ _id : "*actual", files_id : "*result", n : 0, data : { $hex : "11" } }
] }
This diff is collapsed.
<?php
namespace MongoDB\Tests\GridFS;
use MongoDB\GridFS\CollectionWrapper;
use MongoDB\GridFS\WritableStream;
/**
* Functional tests for the internal WritableStream class.
*/
class WritableStreamFunctionalTest extends FunctionalTestCase
{
private $collectionWrapper;
public function setUp()
{
parent::setUp();
$this->collectionWrapper = new CollectionWrapper($this->manager, $this->getDatabaseName(), 'fs');
}
public function testValidConstructorOptions()
{
new WritableStream($this->collectionWrapper, 'filename', [
'_id' => 'custom-id',
'chunkSizeBytes' => 2,
'metadata' => ['foo' => 'bar'],
]);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecks(array $options)
{
new WritableStream($this->collectionWrapper, 'filename', $options);
}
public function provideInvalidConstructorOptions()
{
$options = [];
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = ['chunkSizeBytes' => $value];
}
foreach ($this->getInvalidDocumentValues() as $value) {
$options[][] = ['metadata' => $value];
}
return $options;
}
/**
* @dataProvider provideInputDataAndExpectedMD5
*/
public function testInsertChunksCalculatesMD5($input, $expectedMD5)
{
$stream = new WritableStream($this->collectionWrapper, 'filename');
$stream->insertChunks($input);
$stream->close();
$fileDocument = $this->filesCollection->findOne(
['_id' => $stream->getId()],
['projection' => ['md5' => 1, '_id' => 0]]
);
$this->assertSameDocument(['md5' => $expectedMD5], $fileDocument);
}
public function provideInputDataAndExpectedMD5()
{
return [
['', 'd41d8cd98f00b204e9800998ecf8427e'],
['foobar', '3858f62230ac3c915f300c664312c63f'],
[str_repeat('foobar', 43520), '88ff0e5fcb0acb27947d736b5d69cb73'],
[str_repeat('foobar', 43521), '8ff86511c95a06a611842ceb555d8454'],
[str_repeat('foobar', 87040), '45bfa1a9ec36728ee7338d15c5a30c13'],
[str_repeat('foobar', 87041), '95e78f624f8e745bcfd2d11691fa601e'],
];
}
/**
* @dataProvider provideInputDataAndExpectedMD5
*/
public function testUploadFromStreamCalculatesMD5($input, $expectedMD5)
{
$stream = new WritableStream($this->collectionWrapper, 'filename');
$stream->uploadFromStream($this->createStream($input));
//$stream->close();
$fileDocument = $this->filesCollection->findOne(
['_id' => $stream->getId()],
['projection' => ['md5' => 1, '_id' => 0]]
);
$this->assertSameDocument(['md5' => $expectedMD5], $fileDocument);
}
}
...@@ -165,6 +165,7 @@ class CreateIndexesFunctionalTest extends FunctionalTestCase ...@@ -165,6 +165,7 @@ class CreateIndexesFunctionalTest extends FunctionalTestCase
* given name is found, it will be passed to the callback, which may perform * given name is found, it will be passed to the callback, which may perform
* additional assertions. * additional assertions.
* *
* @param string $indexName
* @param callable $callback * @param callable $callback
*/ */
private function assertIndexExists($indexName, $callback = null) private function assertIndexExists($indexName, $callback = null)
...@@ -185,7 +186,7 @@ class CreateIndexesFunctionalTest extends FunctionalTestCase ...@@ -185,7 +186,7 @@ class CreateIndexesFunctionalTest extends FunctionalTestCase
} }
} }
$this->assertNotNull($foundIndex, sprintf('Found %s index for the collection', $indexName)); $this->assertNotNull($foundIndex, sprintf('Index %s does not exist', $indexName));
if ($callback !== null) { if ($callback !== null) {
call_user_func($callback, $foundIndex); call_user_func($callback, $foundIndex);
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
namespace MongoDB\Tests\Operation; namespace MongoDB\Tests\Operation;
use MongoDB\Driver\Server;
use MongoDB\Operation\DropCollection; use MongoDB\Operation\DropCollection;
use MongoDB\Operation\InsertOne; use MongoDB\Operation\InsertOne;
use MongoDB\Operation\ListCollections; use MongoDB\Operation\ListCollections;
...@@ -20,7 +19,7 @@ class DropCollectionFunctionalTest extends FunctionalTestCase ...@@ -20,7 +19,7 @@ class DropCollectionFunctionalTest extends FunctionalTestCase
$operation = new DropCollection($this->getDatabaseName(), $this->getCollectionName()); $operation = new DropCollection($this->getDatabaseName(), $this->getCollectionName());
$operation->execute($server); $operation->execute($server);
$this->assertCollectionDoesNotExist($server, $this->getDatabaseName(), $this->getCollectionName()); $this->assertCollectionDoesNotExist($this->getCollectionName());
} }
/** /**
...@@ -28,26 +27,22 @@ class DropCollectionFunctionalTest extends FunctionalTestCase ...@@ -28,26 +27,22 @@ class DropCollectionFunctionalTest extends FunctionalTestCase
*/ */
public function testDropNonexistentCollection() public function testDropNonexistentCollection()
{ {
$server = $this->getPrimaryServer(); $this->assertCollectionDoesNotExist($this->getCollectionName());
$this->assertCollectionDoesNotExist($server, $this->getDatabaseName(), $this->getCollectionName());
$operation = new DropCollection($this->getDatabaseName(), $this->getCollectionName()); $operation = new DropCollection($this->getDatabaseName(), $this->getCollectionName());
$operation->execute($server); $operation->execute($this->getPrimaryServer());
} }
/** /**
* Asserts that a collection with the given name does not exist on the * Asserts that a collection with the given name does not exist on the
* server. * server.
* *
* @param Server $server
* @param string $databaseName
* @param string $collectionName * @param string $collectionName
*/ */
private function assertCollectionDoesNotExist(Server $server, $databaseName, $collectionName) private function assertCollectionDoesNotExist($collectionName)
{ {
$operation = new ListCollections($databaseName); $operation = new ListCollections($this->getDatabaseName());
$collections = $operation->execute($server); $collections = $operation->execute($this->getPrimaryServer());
$foundCollection = null; $foundCollection = null;
...@@ -58,6 +53,6 @@ class DropCollectionFunctionalTest extends FunctionalTestCase ...@@ -58,6 +53,6 @@ class DropCollectionFunctionalTest extends FunctionalTestCase
} }
} }
$this->assertNull($foundCollection, sprintf('Collection %s exists on the server', $collectionName)); $this->assertNull($foundCollection, sprintf('Collection %s exists', $collectionName));
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment