. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ namespace tests\unit\Espo\ORM; use Espo\ORM\CollectionFactory; use Espo\ORM\EntityCollection; use Espo\ORM\EntityFactory; use Espo\ORM\EntityManager; use Espo\ORM\Executor\DefaultQueryExecutor; use Espo\ORM\Mapper\BaseMapper; use Espo\ORM\Metadata; use Espo\ORM\MetadataDataProvider; use Espo\ORM\Query\Select; use Espo\ORM\Query\SelectBuilder; use Espo\ORM\QueryComposer\MysqlQueryComposer as QueryComposer; use Espo\ORM\QueryComposer\QueryComposerWrapper; use Espo\ORM\Executor\SqlExecutor; use Espo\ORM\SthCollection; use tests\unit\testData\DB\Comment; use tests\unit\testData\DB\Note; use tests\unit\testData\DB\Post; use tests\unit\testData\DB\Tag; use PDO; use PDOStatement; require_once 'tests/unit/testData/DB/Entities.php'; class MapperTest extends \PHPUnit\Framework\TestCase { protected $db; protected $pdo; protected $post; protected $note; protected $comment; protected $entityFactory; private ?CollectionFactory $collectionFactory = null; protected function setUp() : void { $this->pdo = $this->getMockBuilder(PDO::class)->disableOriginalConstructor()->getMock(); $this->pdo ->expects($this->any()) ->method('quote') ->will($this->returnCallback(function() { $args = func_get_args(); return "'" . $args[0] . "'"; })); $ormMetadata = include('tests/unit/testData/DB/ormMetadata.php'); $metadataDataProvider = $this->createMock(MetadataDataProvider::class); $metadataDataProvider ->expects($this->any()) ->method('get') ->willReturn($ormMetadata); $this->metadata = new Metadata($metadataDataProvider); $this->sqlExecutor = $this->createMock(SqlExecutor::class); $entityManager = $this->createMock(EntityManager::class); $entityManager ->method('getMetadata') ->will($this->returnValue($this->metadata)); $this->entityFactory = $this->createMock(EntityFactory::class); $this->entityFactory ->expects($this->any()) ->method('create') ->will($this->returnCallback( function () use ($entityManager) { $args = func_get_args(); $className = "tests\\unit\\testData\\DB\\" . $args[0]; $defs = $this->metadata->get($args[0]) ?? []; return new $className($args[0], $defs, $entityManager); } )); $entityManager ->method('getEntityFactory') ->will($this->returnValue($this->entityFactory)); $entityFactory = $this->entityFactory; $this->collectionFactory = $this->getMockBuilder(CollectionFactory::class) ->disableOriginalConstructor() ->getMock(); $this->query = new QueryComposer($this->pdo, $this->entityFactory, $this->metadata); $queryExecutor = new DefaultQueryExecutor($this->sqlExecutor, new QueryComposerWrapper($this->query)); $this->db = new BaseMapper( $this->pdo, $this->entityFactory, $this->collectionFactory, $this->metadata, $queryExecutor ); $this->post = $entityFactory->create('Post'); $this->comment = $entityFactory->create('Comment'); $this->tag = $entityFactory->create('Tag'); $this->note = $entityFactory->create('Note'); $this->postData = $entityFactory->create('PostData'); $this->contact = $entityFactory->create('Contact'); $this->account = $entityFactory->create('Account'); $this->team = $entityFactory->create('Team'); } protected function tearDown() : void { unset($this->pdo, $this->db, $this->post, $this->comment); } protected function createSthMock(array $data, bool $noIteration = false) { $sth = $this->getMockBuilder(PDOStatement::class)->disableOriginalConstructor()->getMock(); if (!$noIteration) { $values = []; foreach ($data as $i => $item) { $values[] = $item; } $sth->expects($this->exactly(count($values))) ->method('fetch') ->willReturnOnConsecutiveCalls(...$values); } $sth->expects($this->any()) ->method('fetchAll') ->will($this->returnValue($data)); return $sth; } protected function mockQuery(string $sql, $return = true, $any = false, bool $noIteration = false) { if ($any) { $expects = $this->any(); } else { $expects = $this->once(); } if ($return === true) { $return = $this->createSthMock([]); } else if (is_array($return)) { $return = $this->createSthMock($return, $noIteration); } $this->sqlExecutor->expects($expects) ->method('execute') ->with($sql) ->will($this->returnValue($return)); } protected function mockSqlCollection(string $entityType, string $sql, SthCollection $collection) { $this->collectionFactory ->expects($this->once()) ->method('createFromSql') ->with($entityType, $sql) ->willReturn($collection); } protected function createCollectionMock(array $itemList) : SthCollection { $collection = $this->getMockBuilder(SthCollection::class)->disableOriginalConstructor()->getMock(); $itemList = $itemList ?? []; $generator = (function () use ($itemList) { foreach ($itemList as $item) { yield $item; } })(); $collection ->expects($this->any()) ->method('getIterator') ->will( $this->returnValue($generator) ); return $collection; } public function testSelectOne() { $query = "SELECT post.id AS `id`, post.name AS `name`, NULLIF(TRIM(CONCAT(COALESCE(createdBy.salutation_name, ''), " . "COALESCE(createdBy.first_name, ''), ' ', COALESCE(createdBy.last_name, ''))), '') AS `createdByName`, ". "post.created_by_id AS `createdById`, post.deleted AS `deleted` ". "FROM `post` AS `post` ". "LEFT JOIN `user` AS `createdBy` ON post.created_by_id = createdBy.id " . "WHERE post.id = '1' AND post.deleted = 0"; $return = [ [ 'id' => '1', 'name' => 'test', 'deleted' => false, ], ]; $this->mockQuery($query, $return); $select = Select::fromRaw([ 'from' => 'Post', 'whereClause' => [ 'id' => '1', ], ]); $post = $this->db->selectOne($select); $this->assertEquals($post->id, '1'); } public function testSelect1(): void { $post1 = $this->entityFactory->create('Post'); $post1->set([ 'id' => '2', 'name' => 'test_2', 'deleted' => false, ]); $post2 = $this->entityFactory->create('Post'); $post2->set([ 'id' => '1', 'name' => 'test_1', 'deleted' => false, ]); $collection = $this->createCollectionMock([$post1, $post2]); $query = Select::fromRaw([ 'from' => 'Post', 'fromAlias' => 'post', 'whereClause' => [ 'name' => 'test_1', 'OR' => [ 'id' => '100', 'name*' => 'test_%', ], 'tags.name' => 'yoTag', ], 'order' => 'DESC', 'orderBy' => 'name', 'limit' => 10, 'joins' => [ 'tags', 'comments', ], ]); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $list = $this->db->select($query); $entity = null; foreach ($list as $item) { $entity = $item; break; } $this->assertTrue($entity instanceof Post); $this->assertTrue(isset($entity->id)); $this->assertEquals($entity->id, '2'); } public function testSelectWithSpecifiedParams(): void { $contact = $this->entityFactory->create('Post'); $contact->set([ 'id' => '1', 'name' => 'test', 'deleted' => false, ]); $collection = $this->createCollectionMock([$contact]); $query = Select::fromRaw([ 'from' => 'Contact', 'fromAlias' => 'contact', 'whereClause' => [ 'name*' => 'test%', ], 'order' => 'DESC', 'orderBy' => 'name', 'limit' => 10, ]); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $this->db->select($query); } public function testJoin(): void { $comment = $this->entityFactory->create('Comment'); $comment->set([ 'id' => '11', 'postId' => '1', 'postName' => 'test', 'name' => 'test_comment', 'deleted' => false, ]); $collection = $this->createCollectionMock([$comment]); $query = Select::fromRaw([ 'from' => 'Comment', 'fromAlias' => 'comment', ]); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $list = $this->db->select($query); $entity = null; foreach ($list as $item) { $entity = $item; break; } $this->assertTrue($entity instanceof Comment); $this->assertTrue($entity->has('postName')); $this->assertEquals($entity->get('postName'), 'test'); } public function testSelectRelatedManyMany1() { $tag = $this->entityFactory->create('Tag'); $tag->set([ 'id' => '1', 'name' => 'test', 'deleted' => false, ]); $collection = $this->createCollectionMock([$tag]); $this->post->id = '1'; $query = SelectBuilder::create() ->from('Tag', 'tag') ->select([ '*', ['postTag.role', 'postRole'] ]) ->join('PostTag', 'postTag', [ 'tagId:' => 'id', 'postId' => '1', 'deleted' => false, ]) ->where([]) ->build(); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $list = $this->db->selectRelated($this->post, 'tags'); $entity = null; foreach ($list as $item) { $entity = $item; break; } $this->assertTrue($entity instanceof Tag); $this->assertTrue($entity->has('name')); $this->assertEquals($entity->get('name'), 'test'); } public function testSelectRelatedManyMany2() { $select = Select::fromRaw([ 'select' => ['id', 'postRole'], 'from' => 'Tag', ]); $tag = $this->entityFactory->create('Tag'); $tag->set([ 'id' => '1', 'name' => 'test', 'deleted' => false, ]); $collection = $this->createCollectionMock([$tag]); $this->post->id = '1'; $query = SelectBuilder::create() ->from('Tag', 'tag') ->select([ 'id', ['postTag.role', 'postRole'], ]) ->join('PostTag', 'postTag', [ 'tagId:' => 'id', 'postId' => '1', 'deleted' => false, ]) ->where([]) ->build(); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $this->db->selectRelated($this->post, 'tags', $select); } public function testSelectRelatedManyManyWithConditions() { $team = $this->entityFactory->create('Team'); $team->set([ 'id' => '1', 'name' => 'test', 'deleted' => false, ]); $collection = $this->createCollectionMock([$team]); $this->account->id = '1'; $query = SelectBuilder::create() ->from('Team', 'team') ->select([ '*', ['entityTeam.teamId', 'stub'], ]) ->join('EntityTeam', 'entityTeam', [ 'teamId:' => 'id', 'entityId' => '1', 'entityType' => 'Account', 'deleted' => false, ]) ->where([]) ->build(); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $select = Select::fromRaw([ 'from' => 'Team', 'select' => [ '*', ['entityTeam.teamId', 'stub'], ], ]); $this->db->selectRelated($this->account, 'teams', $select); } public function testSelectRelatedHasChildren() { $note = $this->entityFactory->create('Note'); $note->set([ 'id' => '1', 'name' => 'test', 'deleted' => false, ]); $collection = $this->createCollectionMock([$note]); $query = SelectBuilder::create() ->from('Note', 'note') ->where([ ['parentId' => '1'], ['parentType' => 'Post'], ]) ->build(); $this->collectionFactory ->expects($this->once()) ->method('createFromQuery') ->with($query) ->willReturn($collection); $this->post->id = '1'; $list = $this->db->selectRelated($this->post, 'notes'); $entity = null; foreach ($list as $item) { $entity = $item; break; } $this->assertTrue($entity instanceof Note); $this->assertTrue($entity->has('name')); $this->assertEquals($entity->get('name'), 'test'); } public function testSelectRelatedBelongsTo() { $query = "SELECT ". "post.id AS `id`, post.name AS `name`, NULLIF(TRIM(CONCAT(COALESCE(createdBy.salutation_name, ''), ". "COALESCE(createdBy.first_name, ''), ' ', COALESCE(createdBy.last_name, ''))), '') AS `createdByName`, ". "post.created_by_id AS `createdById`, post.deleted AS `deleted` ". "FROM `post` AS `post` ". "LEFT JOIN `user` AS `createdBy` ON post.created_by_id = createdBy.id " . "WHERE (post.id = '1') AND post.deleted = 0 ". "LIMIT 0, 1"; $return = [ [ 'id' => '1', 'name' => 'test', 'deleted' => false, ], ]; $this->mockQuery($query, $return); $this->comment->id = '11'; $this->comment->set('postId', '1'); $post = $this->db->selectRelated($this->comment, 'post'); $this->assertTrue($post instanceof Post); $this->assertTrue(($post->has('name'))); $this->assertEquals($post->get('name'), 'test'); } public function testCountRelated() { $query = "SELECT COUNT(tag.id) AS `value` ". "FROM `tag` AS `tag` ". "JOIN `post_tag` AS `postTag` ON postTag.tag_id = tag.id AND postTag.post_id = '1' AND postTag.deleted = 0 ". "WHERE tag.deleted = 0"; $return = [ [ 'value' => 1, ], ]; $this->mockQuery($query, $return); $this->post->id = '1'; $count = $this->db->countRelated($this->post, 'tags'); $this->assertEquals($count, 1); } public function testCount1(): void { $sql = "SELECT COUNT(post.id) AS `value` FROM `post` AS `post` WHERE post.deleted = 0"; $select = SelectBuilder::create() ->from('Post') ->build(); $this->mockQuery($sql, ['value' => 1]); $this->db->count($select); } public function testCountWithDistinct(): void { $sql = "SELECT COUNT(asq.id) AS `value` FROM (" . "SELECT DISTINCT post.id AS `id` FROM `post` AS `post` WHERE post.deleted = 0" . ") AS `asq`"; $select = SelectBuilder::create() ->from('Post') ->distinct() ->order('id') ->build(); $this->mockQuery($sql, ['value' => 1]); $this->db->count($select); } public function testInsert() { $query = "INSERT INTO `post` (`id`, `name`) VALUES ('1', 'test')"; $return = true; $this->mockQuery($query, $return); $this->post->reset(); $this->post->id = '1'; $this->post->set('name', 'test'); $this->post->set('privateField', 'dontStoreThis'); $this->db->insert($this->post); } public function testInsertUpdate() { $query = "INSERT INTO `post` (`id`, `name`, `deleted`) VALUES ('1', 'test', 0) " . "ON DUPLICATE KEY UPDATE `name` = 'test', `deleted` = 0"; $return = true; $this->mockQuery($query, $return); $this->post->reset(); $this->post->id = '1'; $this->post->set('name', 'test'); $this->post->set('deleted', false); $this->db->insertOnDuplicateUpdate($this->post, ['name', 'deleted']); } public function testMassInsert() { $query = "INSERT INTO `post` (`id`, `name`) VALUES ('1', 'test1'), ('2', 'test2')"; $return = true; $this->mockQuery($query, $return); $post1 = $this->entityFactory->create('Post'); $post2 = $this->entityFactory->create('Post'); $post1->id = '1'; $post1->set('name', 'test1'); $post2->id = '2'; $post2->set('name', 'test2'); $collection = new EntityCollection([ $post1, $post2, ]); $this->db->massInsert($collection); } public function testUpdate1() { $query = "UPDATE `post` SET post.name = 'test' WHERE post.id = '1' AND post.deleted = 0"; $return = true; $this->mockQuery($query, $return); $this->post->reset(); $this->post->id = '1'; $this->post->set('name', 'test'); $this->db->update($this->post); } public function testUpdate2() { $query = "UPDATE `post` SET post.name = 'test', post.deleted = 0 WHERE post.id = '1' AND post.deleted = 0"; $return = true; $this->mockQuery($query, $return); $this->post->reset(); $this->post->id = '1'; $this->post->set('name', 'test'); $this->post->set('deleted', false); $this->db->update($this->post); } public function testUpdateArray1() { $query = "UPDATE `job` SET job.array = '[\"2\",\"1\"]' WHERE job.id = '1' AND job.deleted = 0"; $this->mockQuery($query, true); $job = $this->entityFactory->create('Job'); $job->id = '1'; $job->setFetched('array', ['1', '2']); $job->set('array', ['2', '1']); $this->db->update($job); } public function testUpdateArray2() { $query = "UPDATE `job` SET job.array = NULL WHERE job.id = '1' AND job.deleted = 0"; $this->mockQuery($query, true); $job = $this->entityFactory->create('Job'); $job->id = '1'; $job->setFetched('array', ['1', '2']); $job->set('array', null); $this->db->update($job); } public function testRemoveManyToOne() { $query = "UPDATE `comment` SET comment.post_id = NULL " . "WHERE comment.id = 'commentId' AND comment.post_id = 'postId' AND comment.deleted = 0"; $this->mockQuery($query, true); $this->post->id = 'postId'; $this->comment->id = 'commentId'; $this->db->unrelate($this->comment, 'post', $this->post); } public function testRemoveAllManyToOne() { $query = "UPDATE `comment` SET comment.post_id = NULL " . "WHERE comment.id = 'commentId' AND comment.deleted = 0"; $this->mockQuery($query, true); $this->comment->id = 'commentId'; $this->db->unrelateAll($this->comment, 'post'); } public function testRemoveChildrenToParent() { $query = "UPDATE `note` SET note.parent_id = NULL, note.parent_type = NULL " . "WHERE note.id = 'noteId' AND note.parent_id = 'postId' AND note.parent_type = 'Post' AND note.deleted = 0"; $this->mockQuery($query, true); $this->note->id = 'noteId'; $this->post->id = 'postId'; $this->db->unrelate($this->note, 'parent', $this->post); } public function testRemoveAllChildrenToParent() { $query = "UPDATE `note` SET note.parent_id = NULL, note.parent_type = NULL " . "WHERE note.id = 'noteId' AND note.deleted = 0"; $this->mockQuery($query, true); $this->note->id = 'noteId'; $this->db->unrelateAll($this->note, 'parent'); } public function testRemoveOneToMany() { $query = "UPDATE `comment` SET comment.post_id = NULL " . "WHERE comment.id = 'commentId' AND comment.post_id = 'postId' AND comment.deleted = 0"; $this->mockQuery($query, true); $this->post->id = 'postId'; $this->db->unrelateById($this->post, 'comments', 'commentId'); } public function testRemoveAllOneToMany() { $query = "UPDATE `comment` SET comment.post_id = NULL " . "WHERE comment.post_id = 'postId' AND comment.deleted = 0"; $this->mockQuery($query, true); $this->post->id = 'postId'; $this->db->unrelateAll($this->post, 'comments'); } public function testRemoveOneToOne1() { $this->post->id = 'postId'; $this->postData->id = 'dataId'; $query = "UPDATE `post_data` SET post_data.post_id = NULL " . "WHERE post_data.post_id = 'postId' AND post_data.deleted = 0"; $this->mockQuery($query, true); $this->db->unrelateById($this->post, 'postData', 'dataId'); } public function testRemovenParentToChildren() { $query = "UPDATE `note` SET note.parent_id = NULL, note.parent_type = NULL " . "WHERE note.id = 'noteId' AND note.parent_id = 'postId' AND note.parent_type = 'Post' AND note.deleted = 0"; $this->mockQuery($query, true); $this->post->id = 'postId'; $this->db->unrelateById($this->post, 'notes', 'noteId'); } public function testRemoveAllParentToChildren() { $query = "UPDATE `note` SET note.parent_id = NULL, note.parent_type = NULL " . "WHERE note.parent_id = 'postId' AND note.parent_type = 'Post' AND note.deleted = 0"; $this->mockQuery($query, true); $this->post->id = 'postId'; $this->db->unrelateAll($this->post, 'notes'); } public function testRemoveManyMany() { $query = "UPDATE `post_tag` SET post_tag.deleted = 1 WHERE post_tag.post_id = '1' AND post_tag.tag_id = '100'"; $return = true; $this->mockQuery($query, $return); $this->post->id = '1'; $this->db->unrelateById($this->post, 'tags', '100'); } public function testRemoveAllManyMany() { $query = "UPDATE `post_tag` SET post_tag.deleted = 1 WHERE post_tag.post_id = '1'"; $return = true; $this->mockQuery($query, $return); $this->post->id = '1'; $this->db->unrelateAll($this->post, 'tags'); } public function testRemoveRelationManyManyWithCondition() { $query = "UPDATE `entity_team` SET entity_team.deleted = 1 ". "WHERE entity_team.entity_id = '1' AND entity_team.team_id = '100' AND entity_team.entity_type = 'Account'"; $this->mockQuery($query, true); $this->account->id = '1'; $this->db->unrelateById($this->account, 'teams', '100'); } public function testRemoveAllManyManyWithCondition() { $query = "UPDATE `entity_team` SET entity_team.deleted = 1 ". "WHERE entity_team.entity_id = '1' AND entity_team.entity_type = 'Account'"; $this->mockQuery($query, true); $this->account->id = '1'; $this->db->unrelateAll($this->account, 'teams'); } public function testUnrelateManyToMany() { $query = "UPDATE `post_tag` SET post_tag.deleted = 1 WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'"; $this->mockQuery($query, true); $this->post->id = 'postId'; $this->tag->id = 'tagId'; $this->db->unrelate($this->post, 'tags', $this->tag); } public function testRelateOneToMany() { $this->post->id = 'p'; $this->comment->id = 'c'; $query1 = "SELECT COUNT(comment.id) AS `value` FROM `comment` AS `comment` " . "WHERE comment.id = 'c' AND comment.deleted = 0"; $query2 = "UPDATE `comment` SET comment.post_id = 'p' WHERE comment.id = 'c' AND comment.deleted = 0"; $this->sqlExecutor->expects($this->exactly(2)) ->method('execute') ->withConsecutive([$query1], [$query2]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $this->createSthMock([]) ); $this->db->relate($this->post, 'comments', $this->comment); } public function testRelateParentToChildren() { $this->post->id = 'p'; $this->note->id = 'n'; $query1 = "SELECT COUNT(note.id) AS `value` FROM `note` AS `note` " . "WHERE note.id = 'n' AND note.deleted = 0"; $query2 = "UPDATE `note` SET note.parent_id = 'p', note.parent_type = 'Post' WHERE note.id = 'n' AND note.deleted = 0"; $this->sqlExecutor->expects($this->exactly(2)) ->method('execute') ->withConsecutive([$query1], [$query2]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $this->createSthMock([]) ); $this->db->relate($this->post, 'notes', $this->note); } public function testRelateManyToOne() { $this->comment->id = 'c'; $this->post->id = 'p'; $query1 = "UPDATE `comment` SET comment.post_id = 'p' WHERE comment.id = 'c' AND comment.deleted = 0"; $this->sqlExecutor->expects($this->exactly(1)) ->method('execute') ->withConsecutive([$query1]) ->willReturnOnConsecutiveCalls($this->createSthMock([])); $this->db->relate($this->comment, 'post', $this->post); } public function testRelateChildrenToParent() { $this->note->id = 'n'; $this->post->id = 'p'; $query1 = "UPDATE `note` SET note.parent_id = 'p', note.parent_type = 'Post' WHERE note.id = 'n' AND note.deleted = 0"; $this->sqlExecutor->expects($this->exactly(1)) ->method('execute') ->withConsecutive([$query1]) ->willReturnOnConsecutiveCalls($this->createSthMock([])); $this->db->relate($this->note, 'parent', $this->post); } public function testRelateOneToOne1() { $this->post->id = 'p'; $this->postData->id = 'd'; $query1 = "UPDATE `post_data` SET post_data.post_id = NULL WHERE post_data.id <> 'd' AND post_data.post_id = 'p' AND post_data.deleted = 0"; $query2 = "UPDATE `post_data` SET post_data.post_id = 'p' WHERE post_data.id = 'd' AND post_data.deleted = 0"; $this->sqlExecutor->expects($this->exactly(2)) ->method('execute') ->withConsecutive([$query1], [$query2]) ->willReturnOnConsecutiveCalls($this->createSthMock([]), $this->createSthMock([])); $this->db->relate($this->postData, 'post', $this->post); } public function testRelateOneToOne2() { $this->post->id = 'p'; $this->postData->id = 'd'; $query1 = "SELECT COUNT(postData.id) AS `value` FROM `post_data` AS `postData` " . "WHERE postData.id = 'd' AND postData.deleted = 0"; $query2 = "UPDATE `post_data` SET post_data.post_id = NULL WHERE post_data.post_id = 'p' AND post_data.deleted = 0"; $query3 = "UPDATE `post_data` SET post_data.post_id = 'p' WHERE post_data.id = 'd' AND post_data.deleted = 0"; $this->sqlExecutor->expects($this->exactly(3)) ->method('execute') ->withConsecutive([$query1], [$query2], [$query3]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $this->createSthMock([]), $this->createSthMock([]) ); $this->db->relate($this->post, 'postData', $this->postData); } public function testRelateManyToMany1Insert() { $this->post->id = 'postId'; $this->tag->id = 'tagId'; $query1 = "SELECT COUNT(tag.id) AS `value` FROM `tag` AS `tag` " . "WHERE tag.id = 'tagId' AND tag.deleted = 0"; $query2 = "SELECT post_tag.id AS `id` FROM `post_tag` " . "WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'"; $query3 = "INSERT INTO `post_tag` (`post_id`, `tag_id`, `role`) VALUES ('postId', 'tagId', 'Test') " . "ON DUPLICATE KEY UPDATE `deleted` = 0, `role` = 'Test'"; $ps = $this->createMock(\PDOStatement::class); $ps->expects($this->exactly(1)) ->method('rowCount') ->willReturn(0); $this->sqlExecutor ->expects($this->exactly(3)) ->method('execute') ->withConsecutive([$query1], [$query2], [$query3]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $ps, $this->createSthMock([]) ); $this->db->relate($this->post, 'tags', $this->tag, ['role' => 'Test']); } public function testRelateManyToMany1Update() { $this->post->id = 'postId'; $this->tag->id = 'tagId'; $query1 = "SELECT COUNT(tag.id) AS `value` FROM `tag` AS `tag` " . "WHERE tag.id = 'tagId' AND tag.deleted = 0"; $query2 = "SELECT post_tag.id AS `id` FROM `post_tag` " . "WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'"; $query3 = "UPDATE `post_tag` SET post_tag.deleted = 0, post_tag.role = 'Test' " . "WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'"; $ps = $this->createMock(\PDOStatement::class); $ps->expects($this->exactly(1)) ->method('rowCount') ->willReturn(1); $this->sqlExecutor ->expects($this->exactly(3)) ->method('execute') ->withConsecutive([$query1], [$query2], [$query3]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $ps, $this->createSthMock([]) ); $this->db->relate($this->post, 'tags', $this->tag, ['role' => 'Test']); } public function testRelateManyToMany2Insert() { $this->account->id = 'accountId'; $this->team->id = 'teamId'; $query1 = "SELECT COUNT(team.id) AS `value` FROM `team` AS `team` " . "WHERE team.id = 'teamId' AND team.deleted = 0"; $query2 = "SELECT entity_team.id AS `id` FROM `entity_team` " . "WHERE entity_team.entity_id = 'accountId' AND entity_team.team_id = 'teamId' AND entity_team.entity_type = 'Account'"; $query3 = "INSERT INTO `entity_team` (`entity_id`, `team_id`, `entity_type`) VALUES ('accountId', 'teamId', 'Account') " . "ON DUPLICATE KEY UPDATE `deleted` = 0"; $ps = $this->createMock(\PDOStatement::class); $ps->expects($this->exactly(1)) ->method('rowCount') ->willReturn(0); $this->sqlExecutor ->expects($this->exactly(3)) ->method('execute') ->withConsecutive([$query1], [$query2], [$query3]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $ps, $this->createSthMock([]) ); $this->db->relate($this->account, 'teams', $this->team); } public function testRelateManyToMany2Update() { $this->account->id = 'accountId'; $this->team->id = 'teamId'; $query1 = "SELECT COUNT(team.id) AS `value` FROM `team` AS `team` " . "WHERE team.id = 'teamId' AND team.deleted = 0"; $query2 = "SELECT entity_team.id AS `id` FROM `entity_team` " . "WHERE entity_team.entity_id = 'accountId' AND entity_team.team_id = 'teamId' AND entity_team.entity_type = 'Account'"; $query3 = "UPDATE `entity_team` SET entity_team.deleted = 0 " . "WHERE entity_team.entity_id = 'accountId' AND entity_team.team_id = 'teamId' AND entity_team.entity_type = 'Account'"; $ps = $this->createMock(\PDOStatement::class); $ps->expects($this->exactly(1)) ->method('rowCount') ->willReturn(1); $this->sqlExecutor ->expects($this->exactly(3)) ->method('execute') ->withConsecutive([$query1], [$query2], [$query3]) ->willReturnOnConsecutiveCalls( $this->createSthMock([['value' => 1]]), $ps, $this->createSthMock([]) ); $this->db->relate($this->account, 'teams', $this->team); } public function testGetRelationColumn() { $this->post->id = 'postId'; $this->tag->id = 'tagId'; $query = "SELECT post_tag.role AS `value` FROM `post_tag` " . "WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId' AND post_tag.deleted = 0"; $this->mockQuery($query, [['value' => 'test']]); $result = $this->db->getRelationColumn($this->post, 'tags', 'tagId', 'role'); $this->assertEquals('test', $result); } public function testUpdateRelationColumns() { $this->post->id = 'postId'; $this->tag->id = 'tagId'; $query = "UPDATE `post_tag` SET post_tag.role = 'test' " . "WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId' AND post_tag.deleted = 0"; $this->mockQuery($query); $this->db->updateRelationColumns($this->post, 'tags', 'tagId', [ 'role' => 'test' ]); } public function testMax() { $query = "SELECT MAX(post.id) AS `value` FROM `post` AS `post` WHERE post.deleted = 0"; $return = [ [ 'value' => 10, ] ]; $this->mockQuery($query, $return); $value = $this->db->max(Select::fromRaw(['from' => 'Post']), 'id'); $this->assertEquals($value, 10); } public function testMassRelate() { $query = "INSERT INTO `post_tag` (`post_id`, `tag_id`) ". "SELECT '1' AS `v0`, tag.id AS `id` FROM `tag` WHERE tag.name = 'test' AND tag.deleted = 0 ". "ON DUPLICATE KEY UPDATE `deleted` = 0"; $return = true; $this->mockQuery($query, $return); $this->post->id = '1'; $select = Select::fromRaw([ 'from' => 'Tag', 'whereClause' => [ 'name' => 'test', ], ]); $this->db->massRelate($this->post, 'tags', $select); } public function testDeleteFromDb1() { $query = "DELETE FROM `comment` WHERE comment.id = '1'"; $this->mockQuery($query); $this->db->deleteFromDb('Comment', '1'); } public function testDeleteFromDb2() { $query = "DELETE FROM `comment` WHERE comment.id = '1' AND comment.deleted = 1"; $this->mockQuery($query); $this->db->deleteFromDb('Comment', '1', true); } public function testRestoreDeleted() { $query = "UPDATE `comment` SET comment.deleted = 0 WHERE comment.id = '1'"; $this->mockQuery($query); $this->db->restoreDeleted('Comment', '1'); } }