PHPLIB-510: Add support for BSON type assertions in DocumentsMatchConstraint

parent f4406d71
...@@ -4,9 +4,28 @@ namespace MongoDB\Tests\SpecTests; ...@@ -4,9 +4,28 @@ namespace MongoDB\Tests\SpecTests;
use ArrayObject; use ArrayObject;
use InvalidArgumentException; use InvalidArgumentException;
use MongoDB\BSON\BinaryInterface;
use MongoDB\BSON\DBPointer;
use MongoDB\BSON\Decimal128;
use MongoDB\BSON\Int64;
use MongoDB\BSON\Javascript;
use MongoDB\BSON\MaxKey;
use MongoDB\BSON\MinKey;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\Regex;
use MongoDB\BSON\Symbol;
use MongoDB\BSON\Timestamp;
use MongoDB\BSON\Undefined;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Model\BSONArray; use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument; use MongoDB\Model\BSONDocument;
use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\IsInstanceOf;
use PHPUnit\Framework\Constraint\IsNull;
use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\Constraint\LogicalAnd;
use PHPUnit\Framework\Constraint\LogicalNot;
use PHPUnit\Framework\Constraint\LogicalOr;
use RuntimeException; use RuntimeException;
use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Comparator\Factory; use SebastianBergmann\Comparator\Factory;
...@@ -18,7 +37,9 @@ use function in_array; ...@@ -18,7 +37,9 @@ use function in_array;
use function is_array; use function is_array;
use function is_object; use function is_object;
use function is_scalar; use function is_scalar;
use function method_exists;
use function sprintf; use function sprintf;
use const PHP_INT_SIZE;
/** /**
* Constraint that checks if one document matches another. * Constraint that checks if one document matches another.
...@@ -112,6 +133,128 @@ class DocumentsMatchConstraint extends Constraint ...@@ -112,6 +133,128 @@ class DocumentsMatchConstraint extends Constraint
} }
} }
/**
* @param string $expectedType
* @param mixed $actualValue
*/
private function assertBSONType($expectedType, $actualValue)
{
switch ($expectedType) {
case 'double':
(new IsType('float'))->evaluate($actualValue);
return;
case 'string':
(new IsType('string'))->evaluate($actualValue);
return;
case 'object':
$constraints = [
new IsType('object'),
new LogicalNot(new IsInstanceOf(BSONArray::class)),
];
// LogicalAnd::fromConstraints was introduced in PHPUnit 6.5.0.
// This check can be removed when the PHPUnit dependency is bumped to that version
if (method_exists(LogicalAnd::class, 'fromConstraints')) {
$constraint = LogicalAnd::fromConstraints(...$constraints);
} else {
$constraint = new LogicalAnd();
$constraint->setConstraints($constraints);
}
$constraint->evaluate($actualValue);
return;
case 'array':
$constraints = [
new IsType('array'),
new IsInstanceOf(BSONArray::class),
];
// LogicalOr::fromConstraints was introduced in PHPUnit 6.5.0.
// This check can be removed when the PHPUnit dependency is bumped to that version
if (method_exists(LogicalOr::class, 'fromConstraints')) {
$constraint = LogicalOr::fromConstraints(...$constraints);
} else {
$constraint = new LogicalOr();
$constraint->setConstraints($constraints);
}
$constraint->evaluate($actualValue);
return;
case 'binData':
(new IsInstanceOf(BinaryInterface::class))->evaluate($actualValue);
return;
case 'undefined':
(new IsInstanceOf(Undefined::class))->evaluate($actualValue);
return;
case 'objectId':
(new IsInstanceOf(ObjectId::class))->evaluate($actualValue);
return;
case 'boolean':
(new IsType('bool'))->evaluate($actualValue);
return;
case 'date':
(new IsInstanceOf(UTCDateTime::class))->evaluate($actualValue);
return;
case 'null':
(new IsNull())->evaluate($actualValue);
return;
case 'regex':
(new IsInstanceOf(Regex::class))->evaluate($actualValue);
return;
case 'dbPointer':
(new IsInstanceOf(DBPointer::class))->evaluate($actualValue);
return;
case 'javascript':
(new IsInstanceOf(Javascript::class))->evaluate($actualValue);
return;
case 'symbol':
(new IsInstanceOf(Symbol::class))->evaluate($actualValue);
return;
case 'int':
(new IsType('int'))->evaluate($actualValue);
return;
case 'timestamp':
(new IsInstanceOf(Timestamp::class))->evaluate($actualValue);
return;
case 'long':
if (PHP_INT_SIZE == 4) {
(new IsInstanceOf(Int64::class))->evaluate($actualValue);
} else {
(new IsType('int'))->evaluate($actualValue);
}
return;
case 'decimal':
(new IsInstanceOf(Decimal128::class))->evaluate($actualValue);
return;
case 'minKey':
(new IsInstanceOf(MinKey::class))->evaluate($actualValue);
return;
case 'maxKey':
(new IsInstanceOf(MaxKey::class))->evaluate($actualValue);
return;
}
}
/** /**
* Compares two documents recursively. * Compares two documents recursively.
* *
...@@ -144,6 +287,11 @@ class DocumentsMatchConstraint extends Constraint ...@@ -144,6 +287,11 @@ class DocumentsMatchConstraint extends Constraint
$actualValue = $actual[$key]; $actualValue = $actual[$key];
if ($expectedValue instanceof BSONDocument && isset($expectedValue['$$type'])) {
$this->assertBSONType($expectedValue['$$type'], $actualValue);
continue;
}
if (($expectedValue instanceof BSONArray && $actualValue instanceof BSONArray) || if (($expectedValue instanceof BSONArray && $actualValue instanceof BSONArray) ||
($expectedValue instanceof BSONDocument && $actualValue instanceof BSONDocument)) { ($expectedValue instanceof BSONDocument && $actualValue instanceof BSONDocument)) {
$this->assertEquals($expectedValue, $actualValue, $this->ignoreExtraKeysInEmbedded, $keyPrefix . $key . '.'); $this->assertEquals($expectedValue, $actualValue, $this->ignoreExtraKeysInEmbedded, $keyPrefix . $key . '.');
......
...@@ -2,9 +2,23 @@ ...@@ -2,9 +2,23 @@
namespace MongoDB\Tests\SpecTests; namespace MongoDB\Tests\SpecTests;
use MongoDB\BSON\Binary;
use MongoDB\BSON\Decimal128;
use MongoDB\BSON\Javascript;
use MongoDB\BSON\MaxKey;
use MongoDB\BSON\MinKey;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\Regex;
use MongoDB\BSON\Timestamp;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Model\BSONArray; use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Tests\TestCase; use MongoDB\Tests\TestCase;
use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\ExpectationFailedException;
use function MongoDB\BSON\fromJSON;
use function MongoDB\BSON\toPHP;
use function unserialize;
use const PHP_INT_SIZE;
class DocumentsMatchConstraintTest extends TestCase class DocumentsMatchConstraintTest extends TestCase
{ {
...@@ -57,6 +71,46 @@ class DocumentsMatchConstraintTest extends TestCase ...@@ -57,6 +71,46 @@ class DocumentsMatchConstraintTest extends TestCase
$this->assertResult(true, $c, ['x' => '42', 'y' => 42, 'z' => ['a' => 24]], 'Exact match'); $this->assertResult(true, $c, ['x' => '42', 'y' => 42, 'z' => ['a' => 24]], 'Exact match');
} }
/**
* @dataProvider provideBSONTypes
*/
public function testBSONTypeAssertions($type, $value)
{
$constraint = new DocumentsMatchConstraint(['x' => ['$$type' => $type]]);
$this->assertResult(true, $constraint, ['x' => $value], 'Type matches');
}
public function provideBSONTypes()
{
$undefined = toPHP(fromJSON('{ "undefined": {"$undefined": true} }'));
$symbol = toPHP(fromJSON('{ "symbol": {"$symbol": "test"} }'));
$dbPointer = toPHP(fromJSON('{ "dbPointer": {"$dbPointer": {"$ref": "phongo.test", "$id" : { "$oid" : "5a2e78accd485d55b405ac12" } }} }'));
return [
'double' => ['double', 1.4],
'string' => ['string', 'foo'],
'object' => ['object', new BSONDocument()],
'array' => ['array', ['foo']],
'binData' => ['binData', new Binary('', 0)],
'undefined' => ['undefined', $undefined->undefined],
'objectId' => ['objectId', new ObjectId()],
'boolean' => ['boolean', true],
'date' => ['date', new UTCDateTime()],
'null' => ['null', null],
'regex' => ['regex', new Regex('.*')],
'dbPointer' => ['dbPointer', $dbPointer->dbPointer],
'javascript' => ['javascript', new Javascript('foo = 1;')],
'symbol' => ['symbol', $symbol->symbol],
'int' => ['int', 1],
'timestamp' => ['timestamp', new Timestamp(0, 0)],
'long' => ['long', PHP_INT_SIZE == 4 ? unserialize('C:18:"MongoDB\BSON\Int64":38:{a:1:{s:7:"integer";s:10:"4294967296";}}') : 4294967296],
'decimal' => ['decimal', new Decimal128('18446744073709551616')],
'minKey' => ['minKey', new MinKey()],
'maxKey' => ['maxKey', new MaxKey()],
];
}
/** /**
* @dataProvider errorMessageProvider * @dataProvider errorMessageProvider
*/ */
......
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