Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge v1.x into v2.x #1524

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions tests/UnifiedSpecTests/Constraint/Matches.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace MongoDB\Tests\UnifiedSpecTests\Constraint;

use LogicException;
use MongoDB\BSON\Document;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Type;
use MongoDB\Model\BSONArray;
Expand All @@ -25,10 +26,13 @@
use function is_int;
use function is_object;
use function ltrim;
use function PHPUnit\Framework\assertInstanceOf;
use function PHPUnit\Framework\assertIsBool;
use function PHPUnit\Framework\assertIsString;
use function PHPUnit\Framework\assertJson;
use function PHPUnit\Framework\assertMatchesRegularExpression;
use function PHPUnit\Framework\assertNotNull;
use function PHPUnit\Framework\assertStringStartsWith;
use function PHPUnit\Framework\assertThat;
use function PHPUnit\Framework\containsOnly;
use function PHPUnit\Framework\isInstanceOf;
Expand All @@ -39,6 +43,7 @@
use function sprintf;
use function str_starts_with;
use function strrchr;
use function trim;

/**
* Constraint that checks if one value matches another.
Expand Down Expand Up @@ -263,6 +268,35 @@ private function assertMatchesOperator(BSONDocument $operator, $actual, string $
return;
}

if ($name === '$$matchAsDocument') {
assertInstanceOf(BSONDocument::class, $operator['$$matchAsDocument'], '$$matchAsDocument requires a BSON document');
assertIsString($actual, '$$matchAsDocument requires actual value to be a JSON string');
assertJson($actual, '$$matchAsDocument requires actual value to be a JSON string');

/* Note: assertJson() accepts array and scalar values, but the spec
* assumes that the JSON string will yield a document. */
assertStringStartsWith('{', trim($actual), '$$matchAsDocument requires actual value to be a JSON string denoting an object');

$actualDocument = Document::fromJSON($actual)->toPHP();
$constraint = new Matches($operator['$$matchAsDocument'], $this->entityMap, allowExtraRootKeys: false);

if (! $constraint->evaluate($actualDocument, '', true)) {
self::failAt(sprintf('%s did not match: %s', (new Exporter())->shortenedExport($actual), $constraint->additionalFailureDescription(null)), $keyPath);
}

return;
}

if ($name === '$$matchAsRoot') {
$constraint = new Matches($operator['$$matchAsRoot'], $this->entityMap, allowExtraRootKeys: true);

if (! $constraint->evaluate($actual, '', true)) {
self::failAt(sprintf('$actual did not match as root-level document: %s', $constraint->additionalFailureDescription(null)), $keyPath);
}

return;
}

if ($name === '$$matchesEntity') {
assertNotNull($this->entityMap, '$$matchesEntity requires EntityMap');
assertIsString($operator['$$matchesEntity'], '$$matchesEntity requires string');
Expand Down
41 changes: 41 additions & 0 deletions tests/UnifiedSpecTests/Constraint/MatchesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public function testFlexibleNumericComparison(): void
$this->assertResult(true, $c, ['x' => 1.0, 'y' => 1.0], 'Float instead of expected int matches');
$this->assertResult(true, $c, ['x' => 1, 'y' => 1], 'Int instead of expected float matches');
$this->assertResult(false, $c, ['x' => 'foo', 'y' => 1.0], 'Different type does not match');

/* Matches uses PHPUnit's comparators, which follow PHP behavior. This
* is more liberal than the comparison logic called for by the unified
* test format. This test can be removed when PHPLIB-1577 is addressed.
*/
$this->assertResult(true, $c, ['x' => '1.0', 'y' => '1'], 'Numeric strings may match ints and floats');
}

public function testDoNotAllowExtraRootKeys(): void
Expand Down Expand Up @@ -171,6 +177,37 @@ public function testOperatorSessionLsid(): void
$this->assertResult(false, $c, ['x' => 1], 'session LSID does not match (embedded)');
}

public function testOperatorMatchAsDocument(): void
{
$c = new Matches(['json' => ['$$matchAsDocument' => ['x' => 1]]]);
$this->assertResult(true, $c, ['json' => '{"x": 1}'], 'JSON document matches');
$this->assertResult(false, $c, ['json' => '{"x": 2}'], 'JSON document does not match');
$this->assertResult(false, $c, ['json' => '{"x": 1, "y": 2}'], 'JSON document cannot contain extra fields');

$c = new Matches(['json' => ['$$matchAsDocument' => ['x' => 1.0]]]);
$this->assertResult(true, $c, ['json' => '{"x": 1}'], 'JSON document matches (flexible numeric comparison)');

$c = new Matches(['json' => ['$$matchAsDocument' => ['x' => ['$$exists' => true]]]]);
$this->assertResult(true, $c, ['json' => '{"x": 1}'], 'JSON document matches (special operators)');
$this->assertResult(false, $c, ['json' => '{"y": 1}'], 'JSON document does not match (special operators)');

$c = new Matches(['json' => ['$$matchAsDocument' => ['x' => ['$$type' => 'objectId']]]]);
$this->assertResult(true, $c, ['json' => '{"x": {"$oid": "57e193d7a9cc81b4027498b5"}}'], 'JSON document matches (extended JSON)');
$this->assertResult(false, $c, ['json' => '{"x": {"$numberDecimal": "1234.5"}}'], 'JSON document does not match (extended JSON)');
}

public function testOperatorMatchAsRoot(): void
{
$c = new Matches(['x' => ['$$matchAsRoot' => ['y' => 2]]]);
$this->assertResult(true, $c, ['x' => ['y' => 2, 'z' => 3]], 'Nested document matches (allow extra fields)');
$this->assertResult(true, $c, ['x' => ['y' => 2.0, 'z' => 3.0]], 'Nested document matches (flexible numeric comparison)');
$this->assertResult(false, $c, ['x' => ['y' => 3, 'z' => 3]], 'Nested document does not match');

$c = new Matches(['x' => ['$$matchAsRoot' => ['y' => ['$$exists' => true]]]]);
$this->assertResult(true, $c, ['x' => ['y' => 2, 'z' => 3]], 'Nested document matches (special operators)');
$this->assertResult(false, $c, ['x' => ['z' => 3]], 'Nested document matches (special operators)');
}

#[DataProvider('errorMessageProvider')]
public function testErrorMessages($expectedMessageRegex, Matches $constraint, $actualValue): void
{
Expand Down Expand Up @@ -302,6 +339,10 @@ public static function operatorErrorMessageProvider()
'$$sessionLsid requires string',
new Matches(['x' => ['$$sessionLsid' => 1]], new EntityMap()),
],
'$$matchAsDocument type' => [
'$$matchAsDocument requires a BSON document',
new Matches(['x' => ['$$matchAsDocument' => 'foo']]),
],
];
}

Expand Down
3 changes: 2 additions & 1 deletion tests/UnifiedSpecTests/UnifiedSpecTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class UnifiedSpecTest extends FunctionalTestCase
'crud/replaceOne-sort' => 'Sort for replace operations is not supported (PHPLIB-1492)',
'crud/updateOne-sort' => 'Sort for update operations is not supported (PHPLIB-1492)',
'crud/bypassDocumentValidation' => 'bypassDocumentValidation is handled by libmongoc (PHPLIB-1576)',
'crud/distinct-hint' => 'Hint for distinct operations is not supported (PHPLIB-1582)',
];

/** @var array<string, string> */
Expand All @@ -60,7 +61,7 @@ class UnifiedSpecTest extends FunctionalTestCase
'valid-pass/expectedEventsForClient-eventType: eventType defaults to command if unset' => 'PHPC does not implement CMAP',
// CSOT is not yet implemented (PHPC-1760)
'valid-pass/collectionData-createOptions: collection is created with the correct options' => 'CSOT is not yet implemented (PHPC-1760)',
'valid-pass/matches-lte-operator: special lte matching operator' => 'CSOT is not yet implemented (PHPC-1760)',
'valid-pass/operator-lte: special lte matching operator' => 'CSOT is not yet implemented (PHPC-1760)',
// libmongoc always adds readConcern to aggregate command
'index-management/search index operations ignore read and write concern: listSearchIndexes ignores read and write concern' => 'libmongoc appends readConcern to aggregate command',
// Uses an invalid object name
Expand Down
2 changes: 1 addition & 1 deletion tests/UnifiedSpecTests/UnifiedTestRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ final class UnifiedTestRunner
* - 1.9: Only createEntities operation is implemented
* - 1.10: Not implemented
* - 1.11: Not implemented, but CMAP is not applicable
* - 1.13: Not implemented
* - 1.13: Only $$matchAsDocument and $$matchAsRoot is implemented
* - 1.14: Not implemented
*/
public const MAX_SCHEMA_VERSION = '1.15';
Expand Down
2 changes: 1 addition & 1 deletion tests/specifications
Submodule specifications updated 40 files
+5 −0 .pre-commit-config.yaml
+14 −8 scripts/check_links.py
+55 −0 scripts/check_md_html.py
+7 −6 scripts/generate_index.py
+65 −57 scripts/migrate_to_md.py
+16 −16 source/auth/auth.md
+2 −2 source/bson-corpus/bson-corpus.md
+1 −0 source/bson-corpus/tests/datetime.json
+6 −6 source/bson-decimal128/decimal128.md
+22 −23 source/client-side-encryption/client-side-encryption.md
+7 −7 source/client-side-encryption/tests/README.md
+4 −4 source/compression/OP_COMPRESSED.md
+1 −1 source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md
+12 −0 source/crud/crud.md
+139 −0 source/crud/tests/unified/distinct-hint.json
+73 −0 source/crud/tests/unified/distinct-hint.yml
+1 −1 source/crud/tests/unified/estimatedDocumentCount.json
+1 −1 source/crud/tests/unified/estimatedDocumentCount.yml
+1 −72 source/mongodb-handshake/handshake.md
+73 −1 source/mongodb-handshake/tests/README.md
+2 −2 source/retryable-reads/tests/unified/estimatedDocumentCount.json
+1 −1 source/retryable-reads/tests/unified/estimatedDocumentCount.yml
+3 −2 source/socks5-support/tests/README.md
+205 −0 source/unified-test-format/tests/valid-fail/operator-matchAsDocument.json
+88 −0 source/unified-test-format/tests/valid-fail/operator-matchAsDocument.yml
+67 −0 source/unified-test-format/tests/valid-fail/operator-matchAsRoot.json
+33 −0 source/unified-test-format/tests/valid-fail/operator-matchAsRoot.yml
+74 −0 source/unified-test-format/tests/valid-pass/expectedError-isClientError.json
+39 −0 source/unified-test-format/tests/valid-pass/expectedError-isClientError.yml
+10 −0 source/unified-test-format/tests/valid-pass/operation-empty_array.json
+7 −0 source/unified-test-format/tests/valid-pass/operation-empty_array.yml
+1 −1 source/unified-test-format/tests/valid-pass/operator-lte.json
+2 −2 source/unified-test-format/tests/valid-pass/operator-lte.yml
+124 −0 source/unified-test-format/tests/valid-pass/operator-matchAsDocument.json
+54 −0 source/unified-test-format/tests/valid-pass/operator-matchAsDocument.yml
+151 −0 source/unified-test-format/tests/valid-pass/operator-matchAsRoot.json
+64 −0 source/unified-test-format/tests/valid-pass/operator-matchAsRoot.yml
+174 −0 source/unified-test-format/tests/valid-pass/operator-type-number_alias.json
+61 −0 source/unified-test-format/tests/valid-pass/operator-type-number_alias.yml
+3 −2 source/unified-test-format/unified-test-format.md