Skip to content

Commit 0dcb97f

Browse files
committed
PHPLIB-510: Add support for BSON type assertions in DocumentsMatchConstraint
1 parent f4406d7 commit 0dcb97f

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

tests/SpecTests/DocumentsMatchConstraint.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,28 @@
44

55
use ArrayObject;
66
use InvalidArgumentException;
7+
use MongoDB\BSON\BinaryInterface;
8+
use MongoDB\BSON\DBPointer;
9+
use MongoDB\BSON\Decimal128;
10+
use MongoDB\BSON\Int64;
11+
use MongoDB\BSON\Javascript;
12+
use MongoDB\BSON\MaxKey;
13+
use MongoDB\BSON\MinKey;
14+
use MongoDB\BSON\ObjectId;
15+
use MongoDB\BSON\Regex;
16+
use MongoDB\BSON\Symbol;
17+
use MongoDB\BSON\Timestamp;
18+
use MongoDB\BSON\Undefined;
19+
use MongoDB\BSON\UTCDateTime;
720
use MongoDB\Model\BSONArray;
821
use MongoDB\Model\BSONDocument;
922
use PHPUnit\Framework\Constraint\Constraint;
23+
use PHPUnit\Framework\Constraint\IsInstanceOf;
24+
use PHPUnit\Framework\Constraint\IsNull;
25+
use PHPUnit\Framework\Constraint\IsType;
26+
use PHPUnit\Framework\Constraint\LogicalAnd;
27+
use PHPUnit\Framework\Constraint\LogicalNot;
28+
use PHPUnit\Framework\Constraint\LogicalOr;
1029
use RuntimeException;
1130
use SebastianBergmann\Comparator\ComparisonFailure;
1231
use SebastianBergmann\Comparator\Factory;
@@ -18,7 +37,9 @@
1837
use function is_array;
1938
use function is_object;
2039
use function is_scalar;
40+
use function method_exists;
2141
use function sprintf;
42+
use const PHP_INT_SIZE;
2243

2344
/**
2445
* Constraint that checks if one document matches another.
@@ -112,6 +133,128 @@ public function evaluate($other, $description = '', $returnResult = false)
112133
}
113134
}
114135

136+
/**
137+
* @param string $expectedType
138+
* @param mixed $actualValue
139+
*/
140+
private function assertBSONType($expectedType, $actualValue)
141+
{
142+
switch ($expectedType) {
143+
case 'double':
144+
(new IsType('float'))->evaluate($actualValue);
145+
146+
return;
147+
case 'string':
148+
(new IsType('string'))->evaluate($actualValue);
149+
150+
return;
151+
case 'object':
152+
$constraints = [
153+
new IsType('object'),
154+
new LogicalNot(new IsInstanceOf(BSONArray::class)),
155+
];
156+
157+
// LogicalAnd::fromConstraints was introduced in PHPUnit 6.5.0.
158+
// This check can be removed when the PHPUnit dependency is bumped to that version
159+
if (method_exists(LogicalAnd::class, 'fromConstraints')) {
160+
$constraint = LogicalAnd::fromConstraints(...$constraints);
161+
} else {
162+
$constraint = new LogicalAnd();
163+
$constraint->setConstraints($constraints);
164+
}
165+
166+
$constraint->evaluate($actualValue);
167+
168+
return;
169+
case 'array':
170+
$constraints = [
171+
new IsType('array'),
172+
new IsInstanceOf(BSONArray::class),
173+
];
174+
175+
// LogicalOr::fromConstraints was introduced in PHPUnit 6.5.0.
176+
// This check can be removed when the PHPUnit dependency is bumped to that version
177+
if (method_exists(LogicalOr::class, 'fromConstraints')) {
178+
$constraint = LogicalOr::fromConstraints(...$constraints);
179+
} else {
180+
$constraint = new LogicalOr();
181+
$constraint->setConstraints($constraints);
182+
}
183+
184+
$constraint->evaluate($actualValue);
185+
186+
return;
187+
case 'binData':
188+
(new IsInstanceOf(BinaryInterface::class))->evaluate($actualValue);
189+
190+
return;
191+
case 'undefined':
192+
(new IsInstanceOf(Undefined::class))->evaluate($actualValue);
193+
194+
return;
195+
case 'objectId':
196+
(new IsInstanceOf(ObjectId::class))->evaluate($actualValue);
197+
198+
return;
199+
case 'boolean':
200+
(new IsType('bool'))->evaluate($actualValue);
201+
202+
return;
203+
case 'date':
204+
(new IsInstanceOf(UTCDateTime::class))->evaluate($actualValue);
205+
206+
return;
207+
case 'null':
208+
(new IsNull())->evaluate($actualValue);
209+
210+
return;
211+
case 'regex':
212+
(new IsInstanceOf(Regex::class))->evaluate($actualValue);
213+
214+
return;
215+
case 'dbPointer':
216+
(new IsInstanceOf(DBPointer::class))->evaluate($actualValue);
217+
218+
return;
219+
case 'javascript':
220+
(new IsInstanceOf(Javascript::class))->evaluate($actualValue);
221+
222+
return;
223+
case 'symbol':
224+
(new IsInstanceOf(Symbol::class))->evaluate($actualValue);
225+
226+
return;
227+
case 'int':
228+
(new IsType('int'))->evaluate($actualValue);
229+
230+
return;
231+
case 'timestamp':
232+
(new IsInstanceOf(Timestamp::class))->evaluate($actualValue);
233+
234+
return;
235+
case 'long':
236+
if (PHP_INT_SIZE == 4) {
237+
(new IsInstanceOf(Int64::class))->evaluate($actualValue);
238+
} else {
239+
(new IsType('int'))->evaluate($actualValue);
240+
}
241+
242+
return;
243+
case 'decimal':
244+
(new IsInstanceOf(Decimal128::class))->evaluate($actualValue);
245+
246+
return;
247+
case 'minKey':
248+
(new IsInstanceOf(MinKey::class))->evaluate($actualValue);
249+
250+
return;
251+
case 'maxKey':
252+
(new IsInstanceOf(MaxKey::class))->evaluate($actualValue);
253+
254+
return;
255+
}
256+
}
257+
115258
/**
116259
* Compares two documents recursively.
117260
*
@@ -144,6 +287,11 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, $ignor
144287

145288
$actualValue = $actual[$key];
146289

290+
if ($expectedValue instanceof BSONDocument && isset($expectedValue['$$type'])) {
291+
$this->assertBSONType($expectedValue['$$type'], $actualValue);
292+
continue;
293+
}
294+
147295
if (($expectedValue instanceof BSONArray && $actualValue instanceof BSONArray) ||
148296
($expectedValue instanceof BSONDocument && $actualValue instanceof BSONDocument)) {
149297
$this->assertEquals($expectedValue, $actualValue, $this->ignoreExtraKeysInEmbedded, $keyPrefix . $key . '.');

tests/SpecTests/DocumentsMatchConstraintTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@
22

33
namespace MongoDB\Tests\SpecTests;
44

5+
use MongoDB\BSON\Binary;
6+
use MongoDB\BSON\Decimal128;
7+
use MongoDB\BSON\Javascript;
8+
use MongoDB\BSON\MaxKey;
9+
use MongoDB\BSON\MinKey;
10+
use MongoDB\BSON\ObjectId;
11+
use MongoDB\BSON\Regex;
12+
use MongoDB\BSON\Timestamp;
13+
use MongoDB\BSON\UTCDateTime;
514
use MongoDB\Model\BSONArray;
15+
use MongoDB\Model\BSONDocument;
616
use MongoDB\Tests\TestCase;
717
use PHPUnit\Framework\ExpectationFailedException;
18+
use function MongoDB\BSON\fromJSON;
19+
use function MongoDB\BSON\toPHP;
20+
use function unserialize;
21+
use const PHP_INT_SIZE;
822

923
class DocumentsMatchConstraintTest extends TestCase
1024
{
@@ -57,6 +71,46 @@ public function testPlaceholders()
5771
$this->assertResult(true, $c, ['x' => '42', 'y' => 42, 'z' => ['a' => 24]], 'Exact match');
5872
}
5973

74+
/**
75+
* @dataProvider provideBSONTypes
76+
*/
77+
public function testBSONTypeAssertions($type, $value)
78+
{
79+
$constraint = new DocumentsMatchConstraint(['x' => ['$$type' => $type]]);
80+
81+
$this->assertResult(true, $constraint, ['x' => $value], 'Type matches');
82+
}
83+
84+
public function provideBSONTypes()
85+
{
86+
$undefined = toPHP(fromJSON('{ "undefined": {"$undefined": true} }'));
87+
$symbol = toPHP(fromJSON('{ "symbol": {"$symbol": "test"} }'));
88+
$dbPointer = toPHP(fromJSON('{ "dbPointer": {"$dbPointer": {"$ref": "phongo.test", "$id" : { "$oid" : "5a2e78accd485d55b405ac12" } }} }'));
89+
90+
return [
91+
'double' => ['double', 1.4],
92+
'string' => ['string', 'foo'],
93+
'object' => ['object', new BSONDocument()],
94+
'array' => ['array', ['foo']],
95+
'binData' => ['binData', new Binary('', 0)],
96+
'undefined' => ['undefined', $undefined->undefined],
97+
'objectId' => ['objectId', new ObjectId()],
98+
'boolean' => ['boolean', true],
99+
'date' => ['date', new UTCDateTime()],
100+
'null' => ['null', null],
101+
'regex' => ['regex', new Regex('.*')],
102+
'dbPointer' => ['dbPointer', $dbPointer->dbPointer],
103+
'javascript' => ['javascript', new Javascript('foo = 1;')],
104+
'symbol' => ['symbol', $symbol->symbol],
105+
'int' => ['int', 1],
106+
'timestamp' => ['timestamp', new Timestamp(0, 0)],
107+
'long' => ['long', PHP_INT_SIZE == 4 ? unserialize('C:18:"MongoDB\BSON\Int64":38:{a:1:{s:7:"integer";s:10:"4294967296";}}') : 4294967296],
108+
'decimal' => ['decimal', new Decimal128('18446744073709551616')],
109+
'minKey' => ['minKey', new MinKey()],
110+
'maxKey' => ['maxKey', new MaxKey()],
111+
];
112+
}
113+
60114
/**
61115
* @dataProvider errorMessageProvider
62116
*/

0 commit comments

Comments
 (0)