createStub(Collection::class); $collection->method('find')->willReturnCallback(function (array $filter) use (&$filters): Cursor { $filters[] = $filter; return new Cursor(new ArrayIterator([['uid' => count($filters)]])); }); $changes = (new MessageStore($this->dataStore($collection)))->changes('svc', 'INBOX', 7, 10); $key = ['sid' => 'svc', 'mailbox' => 'INBOX', 'uidValidity' => 7]; $this->assertSame([ [...$key, 'removedSeq' => null, 'addedSeq' => ['$gt' => 10]], [...$key, 'removedSeq' => null, 'addedSeq' => ['$lte' => 10], 'modSeq' => ['$gt' => 10]], [...$key, 'addedSeq' => ['$lte' => 10], 'removedSeq' => ['$gt' => 10]], ], $filters); $this->assertSame(['additions' => [1], 'modifications' => [2], 'deletions' => [3]], $changes); } public function testTombstoneKeepsKeyAndSequencesOnly(): void { $collection = $this->createMock(Collection::class); $collection->expects($this->once()) ->method('updateMany') ->with( ['sid' => 'svc', 'mailbox' => 'INBOX', 'uidValidity' => 7, 'uid' => ['$in' => [3, 4]], 'removedSeq' => null], $this->callback(function (array $update): bool { $this->assertSame(['removedSeq' => 12, 'modSeq' => 12], $update['$set']); $this->assertArrayHasKey('subject', $update['$unset']); $this->assertArrayHasKey('flags', $update['$unset']); $this->assertArrayNotHasKey('addedSeq', $update['$unset']); return true; }), ); (new MessageStore($this->dataStore($collection)))->tombstone('svc', 'INBOX', 7, 12, 3, 4); } public function testLiveReadsSkipTombstones(): void { $collection = $this->createMock(Collection::class); $collection->expects($this->once()) ->method('find') ->with($this->callback(fn (array $filter): bool => array_key_exists('removedSeq', $filter) && $filter['removedSeq'] === null)) ->willReturn(new Cursor(new ArrayIterator([]))); (new MessageStore($this->dataStore($collection)))->flags('svc', 'INBOX', 7); } public function testReserveSequenceReturnsTheFirstNumberOfTheRange(): void { $mongo = $this->createMock(MongoCollection::class); $mongo->expects($this->once()) ->method('findOneAndUpdate') ->with( ['sid' => 'svc', 'name' => 'INBOX'], ['$inc' => ['changeSeq' => 3]], $this->callback(fn (array $options): bool => $options['returnDocument'] === FindOneAndUpdate::RETURN_DOCUMENT_AFTER), ) ->willReturn(['changeSeq' => 12]); $collection = $this->createStub(Collection::class); $collection->method('getMongoCollection')->willReturn($mongo); $this->assertSame(10, (new MailboxStore($this->dataStore($collection)))->reserveSequence('svc', 'INBOX', 3)); } private function dataStore(Collection $collection): DataStore { $dataStore = $this->createStub(DataStore::class); $dataStore->method('selectCollection')->willReturn($collection); return $dataStore; } }