Skip to content

Commit 2a81aaf

Browse files
PHPLIB-662: Unified test runner should error for unexpected operation arguments (#898)
1 parent 3049b5d commit 2a81aaf

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

tests/UnifiedSpecTests/Operation.php

+8
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ private function execute()
211211
private function executeForChangeStream(ChangeStream $changeStream)
212212
{
213213
$args = $this->prepareArguments();
214+
Util::assertArgumentsBySchema(ChangeStream::class, $this->name, $args);
214215

215216
switch ($this->name) {
216217
case 'iterateUntilDocumentOrError':
@@ -243,6 +244,7 @@ private function executeForChangeStream(ChangeStream $changeStream)
243244
private function executeForClient(Client $client)
244245
{
245246
$args = $this->prepareArguments();
247+
Util::assertArgumentsBySchema(Client::class, $this->name, $args);
246248

247249
switch ($this->name) {
248250
case 'createChangeStream':
@@ -268,6 +270,7 @@ private function executeForClient(Client $client)
268270
private function executeForCollection(Collection $collection)
269271
{
270272
$args = $this->prepareArguments();
273+
Util::assertArgumentsBySchema(Collection::class, $this->name, $args);
271274

272275
switch ($this->name) {
273276
case 'aggregate':
@@ -479,6 +482,7 @@ private function executeForCollection(Collection $collection)
479482
private function executeForCursor(Cursor $cursor)
480483
{
481484
$args = $this->prepareArguments();
485+
Util::assertArgumentsBySchema(Cursor::class, $this->name, $args);
482486

483487
switch ($this->name) {
484488
case 'close':
@@ -526,6 +530,7 @@ private function executeForCursor(Cursor $cursor)
526530
private function executeForDatabase(Database $database)
527531
{
528532
$args = $this->prepareArguments();
533+
Util::assertArgumentsBySchema(Database::class, $this->name, $args);
529534

530535
switch ($this->name) {
531536
case 'aggregate':
@@ -587,6 +592,7 @@ private function executeForDatabase(Database $database)
587592
private function executeForSession(Session $session)
588593
{
589594
$args = $this->prepareArguments();
595+
Util::assertArgumentsBySchema(Session::class, $this->name, $args);
590596

591597
switch ($this->name) {
592598
case 'abortTransaction':
@@ -627,6 +633,7 @@ private function executeForSession(Session $session)
627633
private function executeForBucket(Bucket $bucket)
628634
{
629635
$args = $this->prepareArguments();
636+
Util::assertArgumentsBySchema(Bucket::class, $this->name, $args);
630637

631638
switch ($this->name) {
632639
case 'delete':
@@ -672,6 +679,7 @@ private function executeForBucket(Bucket $bucket)
672679
private function executeForTestRunner()
673680
{
674681
$args = $this->prepareArguments();
682+
Util::assertArgumentsBySchema(self::OBJECT_TEST_RUNNER, $this->name, $args);
675683

676684
switch ($this->name) {
677685
case 'assertCollectionExists':

tests/UnifiedSpecTests/Util.php

+98
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22

33
namespace MongoDB\Tests\UnifiedSpecTests;
44

5+
use MongoDB\ChangeStream;
6+
use MongoDB\Client;
7+
use MongoDB\Collection;
8+
use MongoDB\Database;
9+
use MongoDB\Driver\Cursor;
510
use MongoDB\Driver\ReadConcern;
611
use MongoDB\Driver\ReadPreference;
12+
use MongoDB\Driver\Session;
713
use MongoDB\Driver\WriteConcern;
14+
use MongoDB\GridFS\Bucket;
815
use stdClass;
916

1017
use function array_diff_key;
@@ -26,13 +33,104 @@
2633

2734
final class Util
2835
{
36+
/**
37+
* Array to fill, which contains the schema of allowed attributes for operations.
38+
*/
39+
private static $args = [
40+
Operation::OBJECT_TEST_RUNNER => [
41+
'assertCollectionExists' => ['databaseName', 'collectionName'],
42+
'assertCollectionNotExists' => ['databaseName', 'collectionName'],
43+
'assertIndexExists' => ['databaseName', 'collectionName', 'indexName'],
44+
'assertIndexNotExists' => ['databaseName', 'collectionName', 'indexName'],
45+
'assertSameLsidOnLastTwoCommands' => ['client'],
46+
'assertDifferentLsidOnLastTwoCommands' => ['client'],
47+
'assertNumberConnectionsCheckedOut' => ['connections'],
48+
'assertSessionDirty' => ['session'],
49+
'assertSessionNotDirty' => ['session'],
50+
'assertSessionPinned' => ['session'],
51+
'assertSessionTransactionState' => ['session', 'state'],
52+
'assertSessionUnpinned' => ['session'],
53+
'failPoint' => ['client', 'failPoint'],
54+
'targetedFailPoint' => ['session', 'failPoint'],
55+
'loop' => ['operations', 'storeErrorsAsEntity', 'storeFailuresAsEntity', 'storeSuccessesAsEntity', 'storeIterationsAsEntity'],
56+
],
57+
Client::class => [
58+
'createChangeStream' => ['pipeline', 'session', 'fullDocument', 'resumeAfter', 'startAfter', 'startAtOperationTime', 'batchSize', 'collation', 'maxAwaitTimeMS'],
59+
'listDatabaseNames' => ['authorizedDatabases', 'filter', 'maxTimeMS', 'session'],
60+
'listDatabases' => ['authorizedDatabases', 'filter', 'maxTimeMS', 'session'],
61+
],
62+
Database::class => [
63+
'aggregate' => ['pipeline', 'session', 'useCursor', 'allowDiskUse', 'batchSize', 'bypassDocumentValidation', 'collation', 'comment', 'explain', 'hint', 'let', 'maxAwaitTimeMS', 'maxTimeMS'],
64+
'createChangeStream' => ['pipeline', 'session', 'fullDocument', 'resumeAfter', 'startAfter', 'startAtOperationTime', 'batchSize', 'collation', 'maxAwaitTimeMS'],
65+
'createCollection' => ['collection', 'session', 'autoIndexId', 'capped', 'collation', 'expireAfterSeconds', 'flags', 'indexOptionDefaults', 'max', 'maxTimeMS', 'size', 'storageEngine', 'timeseries', 'validationAction', 'validationLevel', 'validator'],
66+
'dropCollection' => ['collection', 'session'],
67+
'listCollectionNames' => ['authorizedCollections', 'filter', 'maxTimeMS', 'session'],
68+
'listCollections' => ['authorizedCollections', 'filter', 'maxTimeMS', 'session'],
69+
// Note: commandName is not used by PHP
70+
'runCommand' => ['command', 'session', 'commandName'],
71+
],
72+
Collection::class => [
73+
'aggregate' => ['pipeline', 'session', 'useCursor', 'allowDiskUse', 'batchSize', 'bypassDocumentValidation', 'collation', 'comment', 'explain', 'hint', 'let', 'maxAwaitTimeMS', 'maxTimeMS'],
74+
'bulkWrite' => ['requests', 'session', 'ordered', 'bypassDocumentValidation'],
75+
'createChangeStream' => ['pipeline', 'session', 'fullDocument', 'resumeAfter', 'startAfter', 'startAtOperationTime', 'batchSize', 'collation', 'maxAwaitTimeMS'],
76+
'createFindCursor' => ['filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'limit', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'],
77+
'createIndex' => ['keys', 'commitQuorum', 'maxTimeMS', 'name', 'session'],
78+
'dropIndex' => ['name', 'session', 'maxTimeMS'],
79+
'count' => ['filter', 'session', 'collation', 'hint', 'limit', 'maxTimeMS', 'skip'],
80+
'countDocuments' => ['filter', 'session', 'limit', 'skip', 'collation', 'hint', 'maxTimeMS'],
81+
'estimatedDocumentCount' => ['session', 'maxTimeMS'],
82+
'deleteMany' => ['filter', 'session', 'collation', 'hint'],
83+
'deleteOne' => ['filter', 'session', 'collation', 'hint'],
84+
'findOneAndDelete' => ['filter', 'session', 'projection', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'maxTimeMS', 'new', 'sort', 'update', 'upsert'],
85+
'distinct' => ['fieldName', 'filter', 'session', 'collation', 'maxTimeMS'],
86+
'drop' => ['session'],
87+
'find' => ['filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'limit', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'],
88+
'findOne' => ['filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'],
89+
'findOneAndReplace' => ['returnDocument', 'filter', 'replacement', 'session', 'projection', 'returnDocument', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'maxTimeMS', 'new', 'remove', 'sort'],
90+
'replaceOne' => ['filter', 'replacement', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint'],
91+
'findOneAndUpdate' => ['returnDocument', 'filter', 'update', 'session', 'upsert', 'projection', 'remove', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'maxTimeMS', 'sort'],
92+
'updateMany' => ['filter', 'update', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint'],
93+
'updateOne' => ['filter', 'update', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint'],
94+
'insertMany' => ['options', 'documents', 'session', 'ordered', 'bypassDocumentValidation'],
95+
'insertOne' => ['document', 'session', 'bypassDocumentValidation'],
96+
'listIndexes' => ['session', 'maxTimeMS'],
97+
'mapReduce' => ['map', 'reduce', 'out', 'session', 'bypassDocumentValidation', 'collation', 'finalize', 'jsMode', 'limit', 'maxTimeMS', 'query', 'scope', 'sort', 'verbose'],
98+
],
99+
ChangeStream::class => [
100+
'iterateUntilDocumentOrError' => [],
101+
],
102+
Cursor::class => [
103+
'close' => [],
104+
'iterateUntilDocumentOrError' => [],
105+
],
106+
Session::class => [
107+
'abortTransaction' => [],
108+
'commitTransaction' => [],
109+
'endSession' => [],
110+
'startTransaction' => ['maxCommitTimeMS', 'readConcern', 'readPreference', 'writeConcern'],
111+
'withTransaction' => ['callback', 'maxCommitTimeMS', 'readConcern', 'readPreference', 'writeConcern'],
112+
],
113+
Bucket::class => [
114+
'delete' => ['id'],
115+
'downloadByName' => ['filename', 'revision'],
116+
'download' => ['id'],
117+
'uploadWithId' => ['id', 'filename', 'source', 'chunkSizeBytes', 'disableMD5', 'contentType', 'metadata'],
118+
'upload' => ['filename', 'source', 'chunkSizeBytes', 'disableMD5', 'contentType', 'metadata'],
119+
],
120+
];
121+
29122
public static function assertHasOnlyKeys($arrayOrObject, array $keys): void
30123
{
31124
assertThat($arrayOrObject, logicalOr(isType('array'), isInstanceOf(stdClass::class)));
32125
$diff = array_diff_key((array) $arrayOrObject, array_fill_keys($keys, 1));
33126
assertEmpty($diff, 'Unsupported keys: ' . implode(',', array_keys($diff)));
34127
}
35128

129+
public static function assertArgumentsBySchema(string $executingObjectName, string $operation, array $args): void
130+
{
131+
self::assertHasOnlyKeys($args, self::$args[$executingObjectName][$operation]);
132+
}
133+
36134
public static function createReadConcern(stdClass $o): ReadConcern
37135
{
38136
self::assertHasOnlyKeys($o, ['level']);

0 commit comments

Comments
 (0)