@@ -307,8 +307,9 @@ public function testInitialCursorIsNotClosed()
307307
308308 /**
309309 * @expectedException MongoDB\Exception\ResumeTokenException
310+ * @expectedExceptionMessage Resume token not found in change document
310311 */
311- public function testNextCannotExtractResumeToken ()
312+ public function testNextResumeTokenNotFound ()
312313 {
313314 $ pipeline = [['$project ' => ['_id ' => 0 ]]];
314315
@@ -324,8 +325,9 @@ public function testNextCannotExtractResumeToken()
324325
325326 /**
326327 * @expectedException MongoDB\Exception\ResumeTokenException
328+ * @expectedExceptionMessage Resume token not found in change document
327329 */
328- public function testRewindCannotExtractResumeToken ()
330+ public function testRewindResumeTokenNotFound ()
329331 {
330332 $ pipeline = [['$project ' => ['_id ' => 0 ]]];
331333
@@ -337,6 +339,40 @@ public function testRewindCannotExtractResumeToken()
337339 $ changeStream ->rewind ();
338340 }
339341
342+ /**
343+ * @expectedException MongoDB\Exception\ResumeTokenException
344+ * @expectedExceptionMessage Expected resume token to have type "array or object" but found "string"
345+ */
346+ public function testNextResumeTokenInvalidType ()
347+ {
348+ $ pipeline = [['$project ' => ['_id ' => ['$literal ' => 'foo ' ]]]];
349+
350+ $ operation = new Watch ($ this ->manager , $ this ->getDatabaseName (), $ this ->getCollectionName (), $ pipeline , ['maxAwaitTimeMS ' => 100 ]);
351+ $ changeStream = $ operation ->execute ($ this ->getPrimaryServer ());
352+
353+ /* Note: we intentionally do not start iteration with rewind() to ensure
354+ * that we test extraction functionality within next(). */
355+ $ this ->insertDocument (['x ' => 1 ]);
356+
357+ $ changeStream ->next ();
358+ }
359+
360+ /**
361+ * @expectedException MongoDB\Exception\ResumeTokenException
362+ * @expectedExceptionMessage Expected resume token to have type "array or object" but found "string"
363+ */
364+ public function testRewindResumeTokenInvalidType ()
365+ {
366+ $ pipeline = [['$project ' => ['_id ' => ['$literal ' => 'foo ' ]]]];
367+
368+ $ operation = new Watch ($ this ->manager , $ this ->getDatabaseName (), $ this ->getCollectionName (), $ pipeline , ['maxAwaitTimeMS ' => 100 ]);
369+ $ changeStream = $ operation ->execute ($ this ->getPrimaryServer ());
370+
371+ $ this ->insertDocument (['x ' => 1 ]);
372+
373+ $ changeStream ->rewind ();
374+ }
375+
340376 public function testMaxAwaitTimeMS ()
341377 {
342378 /* On average, an acknowledged write takes about 20 ms to appear in a
@@ -427,6 +463,67 @@ public function testRewindExtractsResumeTokenAndNextResumes()
427463 $ this ->assertSameDocument ($ expectedResult , $ changeStream ->current ());
428464 }
429465
466+ /**
467+ * @dataProvider provideTypeMapOptionsAndExpectedChangeDocument
468+ */
469+ public function testTypeMapOption (array $ typeMap , $ expectedChangeDocument )
470+ {
471+ $ operation = new Watch ($ this ->manager , $ this ->getDatabaseName (), $ this ->getCollectionName (), [], ['maxAwaitTimeMS ' => 100 , 'typeMap ' => $ typeMap ]);
472+ $ changeStream = $ operation ->execute ($ this ->getPrimaryServer ());
473+
474+ $ changeStream ->rewind ();
475+ $ this ->assertNull ($ changeStream ->current ());
476+
477+ $ this ->insertDocument (['_id ' => 1 , 'x ' => 'foo ' ]);
478+
479+ $ changeStream ->next ();
480+ $ this ->assertTrue ($ changeStream ->valid ());
481+ $ changeDocument = $ changeStream ->current ();
482+
483+ // Unset the resume token and namespace, which are intentionally omitted
484+ if (is_array ($ changeDocument )) {
485+ unset($ changeDocument ['_id ' ], $ changeDocument ['ns ' ]);
486+ } else {
487+ unset($ changeDocument ->_id , $ changeDocument ->ns );
488+ }
489+
490+ $ this ->assertEquals ($ expectedChangeDocument , $ changeDocument );
491+ }
492+
493+ public function provideTypeMapOptionsAndExpectedChangeDocument ()
494+ {
495+ /* Note: the "_id" and "ns" fields are purposefully omitted because the
496+ * resume token's value cannot be anticipated and the collection name,
497+ * which is generated from the test name, is not available in the data
498+ * provider, respectively. */
499+ return [
500+ [
501+ ['root ' => 'array ' , 'document ' => 'array ' ],
502+ [
503+ 'operationType ' => 'insert ' ,
504+ 'fullDocument ' => ['_id ' => 1 , 'x ' => 'foo ' ],
505+ 'documentKey ' => ['_id ' => 1 ],
506+ ],
507+ ],
508+ [
509+ ['root ' => 'object ' , 'document ' => 'array ' ],
510+ (object ) [
511+ 'operationType ' => 'insert ' ,
512+ 'fullDocument ' => ['_id ' => 1 , 'x ' => 'foo ' ],
513+ 'documentKey ' => ['_id ' => 1 ],
514+ ],
515+ ],
516+ [
517+ ['root ' => 'array ' , 'document ' => 'stdClass ' ],
518+ [
519+ 'operationType ' => 'insert ' ,
520+ 'fullDocument ' => (object ) ['_id ' => 1 , 'x ' => 'foo ' ],
521+ 'documentKey ' => (object ) ['_id ' => 1 ],
522+ ],
523+ ],
524+ ];
525+ }
526+
430527 private function insertDocument ($ document )
431528 {
432529 $ insertOne = new InsertOne ($ this ->getDatabaseName (), $ this ->getCollectionName (), $ document );
0 commit comments