* @author Bernhard Posselt * @copyright 2012 Alessandro Cosentino * @copyright 2012-2014 Bernhard Posselt */ namespace OCA\News\Tests\Unit\Db; use OC\DB\QueryBuilder\Literal; use OC\DB\QueryBuilder\Parameter; use OC\DB\ResultAdapter; 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->never()) ->method('orderBy') ->with('items.last_modified', '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 testReadAll() { $selectbuilder = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $this->db->expects($this->exactly(2)) ->method('getQueryBuilder') ->willReturnOnConsecutiveCalls($selectbuilder, $this->builder); $selectbuilder->expects($this->once()) ->method('select') ->with('items.id') ->will($this->returnSelf()); $selectbuilder->expects($this->once()) ->method('from') ->with('news_items', 'items') ->will($this->returnSelf()); $selectbuilder->expects($this->once()) ->method('innerJoin') ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') ->will($this->returnSelf()); $selectbuilder->expects($this->exactly(2)) ->method('andWhere') ->withConsecutive(['feeds.user_id = :userId'], ['items.id <= :maxItemId']) ->will($this->returnSelf()); $selectbuilder->expects($this->exactly(2)) ->method('setParameter') ->withConsecutive(['userId', 'admin'], ['maxItemId', 4]) ->will($this->returnSelf()); $selectbuilder->expects($this->exactly(1)) ->method('getSQL') ->will($this->returnValue('SQL QUERY')); $selectbuilder->expects($this->exactly(1)) ->method('getParameters') ->will($this->returnValue([])); $result = $this->getMockBuilder(ResultAdapter::class) ->disableOriginalConstructor() ->getMock(); $result->expects($this->once()) ->method('fetchAll') ->willReturn([['id' => 1], ['id' => 2]]); $this->db->expects($this->exactly(1)) ->method('executeQuery') ->with('SQL QUERY') ->willReturn($result); $this->builder->expects($this->once()) ->method('createParameter') ->will($this->returnArgument(0)); $this->builder->expects($this->once()) ->method('update') ->with('news_items') ->will($this->returnSelf()); $this->builder->expects($this->once()) ->method('set') ->with('unread', 'unread') ->will($this->returnSelf()); $this->builder->expects($this->exactly(2)) ->method('andWhere') ->withConsecutive(['id IN (:idList)'], ['unread != :unread']) ->will($this->returnSelf()); $this->builder->expects($this->exactly(2)) ->method('setParameter') ->withConsecutive(['unread', false], ['idList', [1, 2]]) ->will($this->returnSelf()); $this->builder->expects($this->exactly(1)) ->method('getSQL') ->will($this->returnValue('QUERY')); $this->builder->expects($this->exactly(1)) ->method('getParameters') ->will($this->returnValue([])); $this->builder->expects($this->exactly(1)) ->method('getParameterTypes') ->will($this->returnValue([])); $this->db->expects($this->exactly(1)) ->method('executeUpdate') ->with('QUERY'); $this->class->readAll('admin', 4); } public function testPurgeDeletedEmpty() { $this->db->expects($this->never()) ->method('getQueryBuilder'); $this->class->purgeDeleted('jack', 4); } public function testDeleteOverThresholdEmptyFeeds() { $builder1 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $func_builder = $this->getMockBuilder(IFunctionBuilder::class) ->disableOriginalConstructor() ->getMock(); $func = $this->getMockBuilder(IQueryFunction::class) ->disableOriginalConstructor() ->getMock(); $this->db->expects($this->exactly(1)) ->method('getQueryBuilder') ->willReturnOnConsecutiveCalls($builder1); $builder1->expects($this->exactly(2)) ->method('func') ->willReturn($func_builder); $func_builder->expects($this->exactly(1)) ->method('count') ->with('*', 'itemCount') ->willReturn($func); $func_builder->expects($this->exactly(1)) ->method('max') ->with('feeds.articles_per_update') ->willReturn($func); $builder1->expects($this->once()) ->method('select') ->with('feed_id', $func) ->willReturnSelf(); $builder1->expects($this->once()) ->method('selectAlias') ->with($func, 'articlesPerUpdate') ->willReturnSelf(); $builder1->expects($this->once()) ->method('from') ->with('news_items', 'items') ->willReturnSelf(); $builder1->expects($this->once()) ->method('innerJoin') ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('groupBy') ->with('feed_id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('getSQL') ->willReturn('FEED_SQL'); $this->class->deleteOverThreshold(1, true); } public function testDeleteOverThresholdSuccess() { $builder1 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $builder2 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $builder3 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $result1 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $result2 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $result3 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $func_builder = $this->getMockBuilder(IFunctionBuilder::class) ->disableOriginalConstructor() ->getMock(); $func = $this->getMockBuilder(IQueryFunction::class) ->disableOriginalConstructor() ->getMock(); $this->db->expects($this->exactly(3)) ->method('getQueryBuilder') ->willReturnOnConsecutiveCalls($builder1, $builder2, $builder3); $builder1->expects($this->exactly(2)) ->method('func') ->willReturn($func_builder); $func_builder->expects($this->exactly(1)) ->method('count') ->with('*', 'itemCount') ->willReturn($func); $func_builder->expects($this->exactly(1)) ->method('max') ->with('feeds.articles_per_update') ->willReturn($func); $builder1->expects($this->once()) ->method('select') ->with('feed_id', $func) ->willReturnSelf(); $builder1->expects($this->once()) ->method('selectAlias') ->with($func, 'articlesPerUpdate') ->willReturnSelf(); $builder1->expects($this->once()) ->method('from') ->with('news_items', 'items') ->willReturnSelf(); $builder1->expects($this->once()) ->method('innerJoin') ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('groupBy') ->with('feed_id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('getSQL') ->willReturn('FEED_SQL'); $this->db->expects($this->exactly(3)) ->method('executeQuery') ->withConsecutive( ['FEED_SQL'], ['RANGE_SQL', ['feedId' => 5], []], ['RANGE_SQL', ['feedId' => 1], []] ) ->willReturnOnConsecutiveCalls($result1, $result2, $result3); $result1->expects($this->once()) ->method('fetchAll') ->with(2) ->willReturn([ ['itemCount' => 5, 'articlesPerUpdate' => 5, 'feed_id' => 5], ['itemCount' => 1, 'articlesPerUpdate' => 1, 'feed_id' => 1], ]); $builder2->expects($this->once()) ->method('select') ->with('id') ->willReturnSelf(); $builder2->expects($this->once()) ->method('from') ->with('news_items') ->willReturnSelf(); $builder2->expects($this->once()) ->method('where') ->with('feed_id = :feedId') ->willReturnSelf(); $builder2->expects($this->once()) ->method('andWhere') ->with('starred = false') ->willReturnSelf(); $builder2->expects($this->never()) ->method('orderBy') ->with('last_modified', 'DESC') ->willReturnSelf(); $builder2->expects($this->once()) ->method('addOrderBy') ->with('id', 'DESC') ->willReturnSelf(); $builder2->expects($this->exactly(2)) ->method('getSQL') ->willReturn('RANGE_SQL'); $result2->expects($this->once()) ->method('fetchAll') ->with(7) ->willReturn([4, 6, 8]); $result3->expects($this->once()) ->method('fetchAll') ->with(7) ->willReturn([3, 5, 7]); $builder3->expects($this->once()) ->method('delete') ->with('news_items') ->willReturnSelf(); $builder3->expects($this->once()) ->method('where') ->with('id IN (?)') ->willReturnSelf(); $builder3->expects($this->exactly(1)) ->method('getSQL') ->willReturn('DELETE_SQL'); $this->db->expects($this->once()) ->method('executeUpdate') ->with('DELETE_SQL', [[4, 6, 8, 3, 5, 7]], [101]) ->will($this->returnValue(10)); $res = $this->class->deleteOverThreshold(1, true); $this->assertSame(10, $res); } public function testDeleteOverThresholdSuccessUnread() { $builder1 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $builder2 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $builder3 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $result1 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $result2 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $result3 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $func_builder = $this->getMockBuilder(IFunctionBuilder::class) ->disableOriginalConstructor() ->getMock(); $func = $this->getMockBuilder(IQueryFunction::class) ->disableOriginalConstructor() ->getMock(); $this->db->expects($this->exactly(3)) ->method('getQueryBuilder') ->willReturnOnConsecutiveCalls($builder1, $builder2, $builder3); $builder1->expects($this->exactly(2)) ->method('func') ->willReturn($func_builder); $func_builder->expects($this->exactly(1)) ->method('count') ->with('*', 'itemCount') ->willReturn($func); $func_builder->expects($this->exactly(1)) ->method('max') ->with('feeds.articles_per_update') ->willReturn($func); $builder1->expects($this->once()) ->method('select') ->with('feed_id', $func) ->willReturnSelf(); $builder1->expects($this->once()) ->method('selectAlias') ->with($func, 'articlesPerUpdate') ->willReturnSelf(); $builder1->expects($this->once()) ->method('from') ->with('news_items', 'items') ->willReturnSelf(); $builder1->expects($this->once()) ->method('innerJoin') ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('groupBy') ->with('feed_id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('getSQL') ->willReturn('FEED_SQL'); $this->db->expects($this->exactly(3)) ->method('executeQuery') ->withConsecutive( ['FEED_SQL'], ['RANGE_SQL', ['feedId' => 5], []], ['RANGE_SQL', ['feedId' => 1], []] ) ->willReturnOnConsecutiveCalls($result1, $result2, $result3); $result1->expects($this->once()) ->method('fetchAll') ->with(2) ->willReturn([ ['itemCount' => 5, 'articlesPerUpdate' => 5, 'feed_id' => 5], ['itemCount' => 1, 'articlesPerUpdate' => 1, 'feed_id' => 1], ]); $builder2->expects($this->once()) ->method('select') ->with('id') ->willReturnSelf(); $builder2->expects($this->once()) ->method('from') ->with('news_items') ->willReturnSelf(); $builder2->expects($this->once()) ->method('where') ->with('feed_id = :feedId') ->willReturnSelf(); $builder2->expects($this->exactly(2)) ->method('andWhere') ->withConsecutive(['starred = false'], ['unread = false']) ->willReturnSelf(); $builder2->expects($this->never()) ->method('orderBy') ->with('last_modified', 'DESC') ->willReturnSelf(); $builder2->expects($this->once()) ->method('addOrderBy') ->with('id', 'DESC') ->willReturnSelf(); $builder2->expects($this->exactly(2)) ->method('getSQL') ->willReturn('RANGE_SQL'); $result2->expects($this->once()) ->method('fetchAll') ->with(7) ->willReturn([4, 6, 8]); $result3->expects($this->once()) ->method('fetchAll') ->with(7) ->willReturn([3, 5, 7]); $builder3->expects($this->once()) ->method('delete') ->with('news_items') ->willReturnSelf(); $builder3->expects($this->once()) ->method('where') ->with('id IN (?)') ->willReturnSelf(); $builder3->expects($this->exactly(1)) ->method('getSQL') ->willReturn('DELETE_SQL'); $this->db->expects($this->once()) ->method('executeUpdate') ->with('DELETE_SQL', [[4, 6, 8, 3, 5, 7]], [101]) ->will($this->returnValue(10)); $res = $this->class->deleteOverThreshold(1, false); $this->assertSame(10, $res); } public function testDeleteOverThresholdSuccessUnreadSkipsIfUnderThreshold() { $builder1 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $builder2 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $builder3 = $this->getMockBuilder(IQueryBuilder::class) ->disableOriginalConstructor() ->getMock(); $result1 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $result2 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $result3 = $this->getMockBuilder(IResult::class) ->disableOriginalConstructor() ->getMock(); $func_builder = $this->getMockBuilder(IFunctionBuilder::class) ->disableOriginalConstructor() ->getMock(); $func = $this->getMockBuilder(IQueryFunction::class) ->disableOriginalConstructor() ->getMock(); $this->db->expects($this->exactly(3)) ->method('getQueryBuilder') ->willReturnOnConsecutiveCalls($builder1, $builder2, $builder3); $builder1->expects($this->exactly(2)) ->method('func') ->willReturn($func_builder); $func_builder->expects($this->exactly(1)) ->method('count') ->with('*', 'itemCount') ->willReturn($func); $func_builder->expects($this->exactly(1)) ->method('max') ->with('feeds.articles_per_update') ->willReturn($func); $builder1->expects($this->once()) ->method('select') ->with('feed_id', $func) ->willReturnSelf(); $builder1->expects($this->once()) ->method('selectAlias') ->with($func, 'articlesPerUpdate') ->willReturnSelf(); $builder1->expects($this->once()) ->method('from') ->with('news_items', 'items') ->willReturnSelf(); $builder1->expects($this->once()) ->method('innerJoin') ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('groupBy') ->with('feed_id') ->willReturnSelf(); $builder1->expects($this->once()) ->method('getSQL') ->willReturn('FEED_SQL'); $this->db->expects($this->exactly(2)) ->method('executeQuery') ->withConsecutive( ['FEED_SQL'], ['RANGE_SQL', ['feedId' => 5], []] ) ->willReturnOnConsecutiveCalls($result1, $result2, $result3); $result1->expects($this->once()) ->method('fetchAll') ->with(2) ->willReturn([ ['itemCount' => 5, 'articlesPerUpdate' => 5, 'feed_id' => 5], ['itemCount' => 1, 'articlesPerUpdate' => 1, 'feed_id' => 1], ]); $builder2->expects($this->once()) ->method('select') ->with('id') ->willReturnSelf(); $builder2->expects($this->once()) ->method('from') ->with('news_items') ->willReturnSelf(); $builder2->expects($this->once()) ->method('where') ->with('feed_id = :feedId') ->willReturnSelf(); $builder2->expects($this->exactly(2)) ->method('andWhere') ->withConsecutive(['starred = false'], ['unread = false']) ->willReturnSelf(); $builder2->expects($this->never()) ->method('orderBy') ->with('last_modified', 'DESC') ->willReturnSelf(); $builder2->expects($this->once()) ->method('addOrderBy') ->with('id', 'DESC') ->willReturnSelf(); $builder2->expects($this->exactly(1)) ->method('getSQL') ->willReturn('RANGE_SQL'); $result2->expects($this->once()) ->method('fetchAll') ->with(7) ->willReturn([4, 6, 8]); $result3->expects($this->never()) ->method('fetchAll') ->with(7) ->willReturn([3, 5, 7]); $builder3->expects($this->once()) ->method('delete') ->with('news_items') ->willReturnSelf(); $builder3->expects($this->once()) ->method('where') ->with('id IN (?)') ->willReturnSelf(); $builder3->expects($this->exactly(1)) ->method('getSQL') ->willReturn('DELETE_SQL'); $this->db->expects($this->once()) ->method('executeUpdate') ->with('DELETE_SQL', [[4, 6, 8]], [101]) ->will($this->returnValue(10)); $res = $this->class->deleteOverThreshold(3, false); $this->assertSame(10, $res); } }