diff options
Diffstat (limited to 'tests/Unit/Db')
-rw-r--r-- | tests/Unit/Db/FeedMapperTest.php | 92 | ||||
-rw-r--r-- | tests/Unit/Db/FolderMapperTest.php | 90 | ||||
-rw-r--r-- | tests/Unit/Db/ItemMapperTest.php | 2360 | ||||
-rw-r--r-- | tests/Unit/Db/MapperFactoryTest.php | 59 | ||||
-rw-r--r-- | tests/Unit/Db/MapperTestUtility.php | 2 |
5 files changed, 2543 insertions, 60 deletions
diff --git a/tests/Unit/Db/FeedMapperTest.php b/tests/Unit/Db/FeedMapperTest.php index c14b8995f..780bfbc38 100644 --- a/tests/Unit/Db/FeedMapperTest.php +++ b/tests/Unit/Db/FeedMapperTest.php @@ -69,7 +69,7 @@ class FeedMapperTest extends MapperTestUtility ->getMock(); $func = $this->getMockBuilder(IQueryFunction::class) - ->getMock(); + ->getMock(); $funcbuilder->expects($this->once()) ->method('count') @@ -451,4 +451,94 @@ class FeedMapperTest extends MapperTestUtility $result = $this->class->findAllFromFolder(null); $this->assertEquals($this->feeds, $result); } + + /** + * @covers \OCA\News\Db\FeedMapperV2::read + */ + public function testRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.id = :feedId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['feedId', 1]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1); + } + + /** + * @covers \OCA\News\Db\FeedMapperV2::read + */ + public function testReadWithMaxId() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.id = :feedId'], ['items.id =< :maxItemId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['feedId', 1], ['maxItemId', 4]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1, 4); + } }
\ No newline at end of file diff --git a/tests/Unit/Db/FolderMapperTest.php b/tests/Unit/Db/FolderMapperTest.php index dd87b22b5..026c16bc6 100644 --- a/tests/Unit/Db/FolderMapperTest.php +++ b/tests/Unit/Db/FolderMapperTest.php @@ -279,4 +279,94 @@ class FolderMapperTest extends MapperTestUtility $result = $this->class->findAll(); $this->assertEquals($this->folders, $result); } + + /** + * @covers \OCA\News\Db\FolderMapperV2::read + */ + public function testRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.folder_id = :folderId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['folderId', 1]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1); + } + + /** + * @covers \OCA\News\Db\FolderMapperV2::read + */ + public function testReadWithMaxId() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('update') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setValue') + ->with('unread', 0) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :userId'], ['feeds.folder_id = :folderId'], ['items.id =< :maxItemId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['userId', 'admin'], ['folderId', 1], ['maxItemId', 4]) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('getSQL') + ->will($this->returnValue('QUERY')); + + $this->db->expects($this->exactly(1)) + ->method('executeUpdate') + ->with('QUERY'); + + $this->class->read('admin', 1, 4); + } }
\ No newline at end of file diff --git a/tests/Unit/Db/ItemMapperTest.php b/tests/Unit/Db/ItemMapperTest.php new file mode 100644 index 000000000..220805061 --- /dev/null +++ b/tests/Unit/Db/ItemMapperTest.php @@ -0,0 +1,2360 @@ +<?php +/** + * Nextcloud - News + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Alessandro Cosentino <cosenal@gmail.com> + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright 2012 Alessandro Cosentino + * @copyright 2012-2014 Bernhard Posselt + */ + +namespace OCA\News\Tests\Unit\Db; + +use OCA\News\Db\Feed; +use OCA\News\Db\FeedMapperV2; +use OCA\News\Db\Folder; +use OCA\News\Db\Item; +use OCA\News\Db\ItemMapperV2; +use OCA\News\Db\NewsMapperV2; +use OCA\News\Service\Exceptions\ServiceValidationException; +use OCA\News\Utility\Time; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\DB\IResult; +use OCP\DB\QueryBuilder\IExpressionBuilder; +use OCP\DB\QueryBuilder\IFunctionBuilder; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\QueryBuilder\IQueryFunction; +use OCP\IDBConnection; +use Test\TestCase; + +/** + * Class ItemMapperTest + * + * @package OCA\News\Tests\Unit\Db + */ +class ItemMapperTest extends MapperTestUtility +{ + + /** @var Time */ + private $time; + /** @var ItemMapperV2 */ + private $class; + + /** + * @covers \OCA\News\Db\ItemMapperV2::__construct + */ + protected function setUp(): void + { + parent::setUp(); + $this->time = $this->getMockBuilder(Time::class) + ->getMock(); + + $this->class = new ItemMapperV2($this->db, $this->time); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::__construct + */ + public function testSetUpSuccess(): void + { + $this->assertEquals('news_items', $this->class->getTableName()); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAllFromUser + */ + public function testFindAllFromUser() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feeds.user_id = :user_id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('andWhere') + ->with('feeds.deleted_at = 0') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['user_id', 'jack']) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(3)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + ['id' => 5], + null + ); + + $result = $this->class->findAllFromUser('jack', []); + $this->assertEquals([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAllFromUser + */ + public function testFindAllFromUserWithParams() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('createNamedParameter') + ->with('val') + ->will($this->returnValue(':val')); + + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feeds.user_id = :user_id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feeds.deleted_at = 0'], ['key = :val']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['user_id', 'jack']) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(3)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + ['id' => 5], + null + ); + + $result = $this->class->findAllFromUser('jack', ['key' => 'val']); + $this->assertEquals([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAll + */ + public function testFindAll() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('andWhere') + ->with('feeds.deleted_at = 0') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(3)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + ['id' => 5], + null + ); + + $result = $this->class->findAll(); + $this->assertEquals([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + /** + * @covers \OCA\News\Db\ItemMapperV2::findAllForFeed + */ + public function testFindAllForFeed() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feed_id = :feed_identifier') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('setParameter') + ->with('feed_identifier', 4) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(3)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + ['id' => 5], + null + ); + + $result = $this->class->findAllForFeed(4); + $this->assertEquals([Item::fromRow(['id' => 4]), Item::fromRow(['id' => 5])], $result); + } + + public function testFindFromUser() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('where') + ->with('feeds.user_id = :user_id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['items.id = :item_id'], ['feeds.deleted_at = 0']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['user_id', 'jack'], ['item_id', 4]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findFromUser('jack', 4); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testFindByGUIDHash() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('andWhere') + ->withConsecutive(['feed_id = :feed_id'], ['guid_hash = :guid_hash']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('setParameter') + ->withConsecutive(['feed_id', 4], ['guid_hash', 'hash']) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findByGuidHash(4, 'hash'); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testFindForUserByGUIDHash() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive(['feeds.user_id = :user_id'], ['feeds.id = :feed_id'], ['items.guid_hash = :guid_hash']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('setParameter') + ->withConsecutive(['user_id', 'jack'], ['feed_id', 4], ['guid_hash', 'hash']) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findForUserByGuidHash('jack', 4, 'hash'); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testNewest() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('addOrderBy') + ->with('items.id', 'DESC') + ->willReturnSelf(); + + $this->builder->expects($this->exactly(1)) + ->method('where') + ->withConsecutive(['feeds.user_id = :userId']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameter') + ->withConsecutive(['userId', 'jack']) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setMaxResults') + ->with(1) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->newest('jack'); + $this->assertEquals(Item::fromRow(['id' => 4]), $result); + } + + public function testFindAllInFeedAfter() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['feeds.id = :feedId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'feedId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('addOrderBy') + ->with('items.id', 'DESC') + ->willReturnSelf(); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllInFeedAfter('jack', 4, 1610903351, false); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllInFeedAfterHideRead() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('innerJoin') + ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(4)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['feeds.id = :feedId'], + ['items.unread = 1'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'feedId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('orderBy') + ->with('items.updated_date', 'DESC') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('addOrderBy') + ->with('items.id', 'DESC') + ->willReturnSelf(); + + $this->builder->expects($this->once()) + ->method('execute') + ->will($this->returnValue($this->cursor)); + + $this->cursor->expects($this->exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls( + ['id' => 4], + false + ); + + $result = $this->class->findAllInFeedAfter('jack', 4, 1610903351, true); + $this->assertEquals([Item::fromRow(['id' => 4])], $result); + } + + public function testFindAllInFolderAfter() + { + $this->db->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($this->builder); + + $this->builder->expects($this->once()) + ->method('select') + ->with('items.*') + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + ->method('from') + ->with('news_items', 'items') + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(2)) + ->method('innerJoin') + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['feeds', 'news_folders', 'folders', 'feeds.folder_id = folders.id'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(3)) + ->method('andWhere') + ->withConsecutive( + ['items.updated_date >= :updatedSince'], + ['feeds.user_id = :userId'], + ['folders.id = :folderId'] + ) + ->will($this->returnSelf()); + + $this->builder->expects($this->exactly(1)) + ->method('setParameters') + ->with([ + 'updatedSince' => 1610903351, + 'folderId' => 4, + 'userId' => 'jack', + ]) + ->will($this->returnSelf()); + + $this->builder->expects($this->once()) + |