diff --git a/tests/TestCase/ORM/Association/BelongsToManyTest.php b/tests/TestCase/ORM/Association/BelongsToManyTest.php index ad27ca83f5b..7f7007748f4 100644 --- a/tests/TestCase/ORM/Association/BelongsToManyTest.php +++ b/tests/TestCase/ORM/Association/BelongsToManyTest.php @@ -32,6 +32,12 @@ */ class BelongsToManyTest extends TestCase { + /** + * Fixtures + * + * @var array + */ + public $fixtures = ['core.articles', 'core.special_tags', 'core.articles_tags', 'core.tags']; /** * Set up @@ -65,29 +71,6 @@ public function setUp() 'primary' => ['type' => 'primary', 'columns' => ['id']] ] ]); - TableRegistry::set('Articles', $this->article); - TableRegistry::get('ArticlesTags', [ - 'table' => 'articles_tags', - 'schema' => [ - 'article_id' => ['type' => 'integer'], - 'tag_id' => ['type' => 'integer'], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['article_id', 'tag_id']] - ] - ] - ]); - $this->tagsTypeMap = new TypeMap([ - 'Tags.id' => 'integer', - 'id' => 'integer', - 'Tags.name' => 'string', - 'name' => 'string', - ]); - $this->articlesTagsTypeMap = new TypeMap([ - 'ArticlesTags.article_id' => 'integer', - 'article_id' => 'integer', - 'ArticlesTags.tag_id' => 'integer', - 'tag_id' => 'integer', - ]); } /** @@ -339,7 +322,7 @@ public function testCascadeDeleteDependent() */ public function testCascadeDeleteWithCallbacks() { - $articleTag = $this->getMock('Cake\ORM\Table', ['find', 'delete'], []); + $articleTag = TableRegistry::get('ArticlesTags'); $config = [ 'sourceTable' => $this->article, 'targetTable' => $this->tag, @@ -347,47 +330,17 @@ public function testCascadeDeleteWithCallbacks() ]; $association = new BelongsToMany('Tag', $config); $association->junction($articleTag); - $this->article - ->association($articleTag->alias()) - ->conditions(['click_count' => 3]); - - $articleTagOne = new Entity(['article_id' => 1, 'tag_id' => 2]); - $articleTagTwo = new Entity(['article_id' => 1, 'tag_id' => 4]); - $iterator = new \ArrayIterator([ - $articleTagOne, - $articleTagTwo - ]); - - $query = $this->getMock('\Cake\ORM\Query', [], [], '', false); - $query->expects($this->at(0)) - ->method('where') - ->with(['click_count' => 3]) - ->will($this->returnSelf()); - $query->expects($this->at(1)) - ->method('where') - ->with(['article_id' => 1]) - ->will($this->returnSelf()); - - $query->expects($this->any()) - ->method('getIterator') - ->will($this->returnValue($iterator)); - - $articleTag->expects($this->once()) - ->method('find') - ->will($this->returnValue($query)); - - $articleTag->expects($this->at(1)) - ->method('delete') - ->with($articleTagOne, []); - $articleTag->expects($this->at(2)) - ->method('delete') - ->with($articleTagTwo, []); + $this->article->association($articleTag->alias()); - $articleTag->expects($this->never()) - ->method('deleteAll'); + $counter = $this->getMock('StdClass', ['__invoke']); + $counter->expects($this->exactly(2))->method('__invoke'); + $articleTag->eventManager()->on('Model.beforeDelete', $counter); + $this->assertEquals(2, $articleTag->find()->where(['article_id' => 1])->count()); $entity = new Entity(['id' => 1, 'name' => 'PHP']); $association->cascadeDelete($entity); + + $this->assertEquals(0, $articleTag->find()->where(['article_id' => 1])->count()); } /** @@ -527,87 +480,26 @@ public function testUnlinkWithNotPersistedTarget() */ public function testUnlinkSuccess() { - $connection = ConnectionManager::get('test'); - $joint = $this->getMock( - '\Cake\ORM\Table', - ['delete', 'find'], - [['alias' => 'ArticlesTags', 'connection' => $connection]] - ); - $config = [ - 'sourceTable' => $this->article, - 'targetTable' => $this->tag, - 'through' => $joint, - 'joinTable' => 'tags_articles' - ]; - $assoc = $this->article->belongsToMany('Test', $config); - $assoc->junction(); - $this->article->association('ArticlesTags') - ->conditions(['foo' => 1]); - - $query1 = $this->getMock('\Cake\ORM\Query', [], [$connection, $joint]); - $query2 = $this->getMock('\Cake\ORM\Query', [], [$connection, $joint]); - - $joint->expects($this->at(0))->method('find') - ->with('all') - ->will($this->returnValue($query1)); - - $joint->expects($this->at(1))->method('find') - ->with('all') - ->will($this->returnValue($query2)); - - $query1->expects($this->at(0)) - ->method('where') - ->with(['foo' => 1]) - ->will($this->returnSelf()); - $query1->expects($this->at(1)) - ->method('where') - ->with(['article_id' => 1]) - ->will($this->returnSelf()); - $query1->expects($this->at(2)) - ->method('andWhere') - ->with(['tag_id' => 2]) - ->will($this->returnSelf()); - $query1->expects($this->once()) - ->method('union') - ->with($query2) - ->will($this->returnSelf()); - - $query2->expects($this->at(0)) - ->method('where') - ->with(['foo' => 1]) - ->will($this->returnSelf()); - $query2->expects($this->at(1)) - ->method('where') - ->with(['article_id' => 1]) - ->will($this->returnSelf()); - $query2->expects($this->at(2)) - ->method('andWhere') - ->with(['tag_id' => 3]) - ->will($this->returnSelf()); - - $jointEntities = [ - new Entity(['article_id' => 1, 'tag_id' => 2]), - new Entity(['article_id' => 1, 'tag_id' => 3]) - ]; - - $query1->expects($this->once()) - ->method('toArray') - ->will($this->returnValue($jointEntities)); - - $opts = ['markNew' => false]; - $tags = [new Entity(['id' => 2], $opts), new Entity(['id' => 3], $opts)]; - $entity = new Entity(['id' => 1, 'test' => $tags], $opts); + $joint = TableRegistry::get('SpecialTags'); + $articles = TableRegistry::get('Articles'); + $tags = TableRegistry::get('Tags'); - $joint->expects($this->at(2)) - ->method('delete') - ->with($jointEntities[0]); + $assoc = $articles->belongsToMany('Tags', [ + 'sourceTable' => $articles, + 'targetTable' => $tags, + 'through' => $joint, + 'joinTable' => 'special_tags', + ]); + $entity = $articles->get(2, ['contain' => 'Tags']); + $initial = $entity->tags; + $this->assertCount(1, $initial); - $joint->expects($this->at(3)) - ->method('delete') - ->with($jointEntities[1]); + $assoc->unlink($entity, $entity->tags); + $this->assertEmpty($entity->get('tags'), 'Property should be empty'); - $assoc->unlink($entity, $tags); - $this->assertEmpty($entity->get('test')); + $new = $articles->get(2, ['contain' => 'Tags']); + $this->assertCount(0, $new->tags, 'DB should be clean'); + $this->assertSame(3, $tags->find()->count(), 'Tags should still exist'); } /** @@ -618,46 +510,27 @@ public function testUnlinkSuccess() */ public function testUnlinkWithoutPropertyClean() { - $connection = ConnectionManager::get('test'); - $joint = $this->getMock( - '\Cake\ORM\Table', - ['delete', 'find'], - [['alias' => 'ArticlesTags', 'connection' => $connection]] - ); - $config = [ - 'sourceTable' => $this->article, - 'targetTable' => $this->tag, - 'through' => $joint, - 'joinTable' => 'tags_articles' - ]; - $assoc = new BelongsToMany('Test', $config); - $assoc - ->junction() - ->association('tags') - ->conditions(['foo' => 1]); + $joint = TableRegistry::get('SpecialTags'); + $articles = TableRegistry::get('Articles'); + $tags = TableRegistry::get('Tags'); - $joint->expects($this->never())->method('find'); - $opts = ['markNew' => false]; - $jointEntities = [ - new Entity(['article_id' => 1, 'tag_id' => 2]), - new Entity(['article_id' => 1, 'tag_id' => 3]) - ]; - $tags = [ - new Entity(['id' => 2, '_joinData' => $jointEntities[0]], $opts), - new Entity(['id' => 3, '_joinData' => $jointEntities[1]], $opts) - ]; - $entity = new Entity(['id' => 1, 'test' => $tags], $opts); - - $joint->expects($this->at(0)) - ->method('delete') - ->with($jointEntities[0]); + $assoc = $articles->belongsToMany('Tags', [ + 'sourceTable' => $articles, + 'targetTable' => $tags, + 'through' => $joint, + 'joinTable' => 'special_tags', + 'conditions' => ['SpecialTags.highlighted' => true] + ]); + $entity = $articles->get(2, ['contain' => 'Tags']); + $initial = $entity->tags; + $this->assertCount(1, $initial); - $joint->expects($this->at(1)) - ->method('delete') - ->with($jointEntities[1]); + $assoc->unlink($entity, $initial, ['cleanProperty' => false]); + $this->assertNotEmpty($entity->get('tags'), 'Property should not be empty'); + $this->assertEquals($initial, $entity->get('tags'), 'Property should be untouched'); - $assoc->unlink($entity, $tags, ['cleanProperty' => false]); - $this->assertEquals($tags, $entity->get('test')); + $new = $articles->get(2, ['contain' => 'Tags']); + $this->assertCount(0, $new->tags, 'DB should be clean'); } /** @@ -688,74 +561,26 @@ public function testReplaceWithMissingPrimaryKey() */ public function testReplaceLinksUpdateToEmptySet() { - $connection = ConnectionManager::get('test'); - $joint = $this->getMock( - '\Cake\ORM\Table', - ['delete', 'find'], - [['alias' => 'ArticlesTags', 'connection' => $connection]] - ); - $config = [ - 'sourceTable' => $this->article, - 'targetTable' => $this->tag, - 'through' => $joint, - 'joinTable' => 'tags_articles' - ]; - $assoc = $this->getMock( - '\Cake\ORM\Association\BelongsToMany', - ['_collectJointEntities', '_saveTarget'], - ['tags', $config] - ); - $assoc->junction(); - - $this->article - ->association('ArticlesTags') - ->conditions(['foo' => 1]); - - $query1 = $this->getMock( - '\Cake\ORM\Query', - ['where', 'andWhere', 'addDefaultTypes'], - [$connection, $joint] - ); + $joint = TableRegistry::get('ArticlesTags'); + $articles = TableRegistry::get('Articles'); + $tags = TableRegistry::get('Tags'); - $joint->expects($this->at(0))->method('find') - ->with('all') - ->will($this->returnValue($query1)); - - $query1->expects($this->at(0)) - ->method('where') - ->with(['foo' => 1]) - ->will($this->returnSelf()); - $query1->expects($this->at(1)) - ->method('where') - ->with(['article_id' => 1]) - ->will($this->returnSelf()); - - $existing = [ - new Entity(['article_id' => 1, 'tag_id' => 2]), - new Entity(['article_id' => 1, 'tag_id' => 4]), - ]; - $query1->setResult(new \ArrayIterator($existing)); - - $tags = []; - $entity = new Entity(['id' => 1, 'test' => $tags]); - - $assoc->expects($this->once())->method('_collectJointEntities') - ->with($entity, $tags) - ->will($this->returnValue([])); + $assoc = $articles->belongsToMany('Tags', [ + 'sourceTable' => $articles, + 'targetTable' => $tags, + 'through' => $joint, + 'joinTable' => 'articles_tags', + ]); - $joint->expects($this->at(1)) - ->method('delete') - ->with($existing[0]); - $joint->expects($this->at(2)) - ->method('delete') - ->with($existing[1]); + $entity = $articles->get(1, ['contain' => 'Tags']); + $this->assertCount(2, $entity->tags); - $assoc->expects($this->never()) - ->method('_saveTarget'); + $assoc->replaceLinks($entity, []); + $this->assertSame([], $entity->tags, 'Property should be empty'); + $this->assertFalse($entity->dirty('tags'), 'Property should be cleaned'); - $assoc->replaceLinks($entity, $tags); - $this->assertSame([], $entity->tags); - $this->assertFalse($entity->dirty('tags')); + $new = $articles->get(1, ['contain' => 'Tags']); + $this->assertSame([], $entity->tags, 'Should not be data in db'); } /** @@ -767,153 +592,68 @@ public function testReplaceLinksUpdateToEmptySet() */ public function testReplaceLinkSuccess() { - $connection = ConnectionManager::get('test'); - $joint = $this->getMock( - '\Cake\ORM\Table', - ['delete', 'find'], - [['alias' => 'ArticlesTags', 'connection' => $connection]] - ); - $config = [ - 'sourceTable' => $this->article, - 'targetTable' => $this->tag, - 'through' => $joint, - 'joinTable' => 'tags_articles' - ]; - $assoc = $this->getMock( - '\Cake\ORM\Association\BelongsToMany', - ['_collectJointEntities', '_saveTarget'], - ['tags', $config] - ); - $assoc->junction(); - - $this->article - ->association('ArticlesTags') - ->conditions(['foo' => 1]); - - $query1 = $this->getMock( - '\Cake\ORM\Query', - ['where', 'andWhere', 'addDefaultTypes'], - [$connection, $joint] - ); - - $joint->expects($this->at(0))->method('find') - ->with('all') - ->will($this->returnValue($query1)); - - $query1->expects($this->at(0)) - ->method('where') - ->with(['foo' => 1]) - ->will($this->returnSelf()); - $query1->expects($this->at(1)) - ->method('where') - ->with(['article_id' => 1]) - ->will($this->returnSelf()); - - $existing = [ - new Entity(['article_id' => 1, 'tag_id' => 2]), - new Entity(['article_id' => 1, 'tag_id' => 4]), - new Entity(['article_id' => 1, 'tag_id' => 5]), - new Entity(['article_id' => 1, 'tag_id' => 6]) - ]; - $query1->setResult(new \ArrayIterator($existing)); + $joint = TableRegistry::get('ArticlesTags'); + $articles = TableRegistry::get('Articles'); + $tags = TableRegistry::get('Tags'); - $opts = ['markNew' => false]; - $tags = [ - new Entity(['id' => 2], $opts), - new Entity(['id' => 3], $opts), - new Entity(['id' => 6]) - ]; - $entity = new Entity(['id' => 1, 'test' => $tags], $opts); + $assoc = $articles->belongsToMany('Tags', [ + 'sourceTable' => $articles, + 'targetTable' => $tags, + 'through' => $joint, + 'joinTable' => 'articles_tags', + ]); + $entity = $articles->get(1, ['contain' => 'Tags']); - $jointEntities = [ - new Entity(['article_id' => 1, 'tag_id' => 2]) + // 1=existing, 2=removed, 3=new link, & new tag + $tagData = [ + new Entity(['id' => 1], ['markNew' => false]), + new Entity(['id' => 3]), + new Entity(['name' => 'net new']), ]; - $assoc->expects($this->once())->method('_collectJointEntities') - ->with($entity, $tags) - ->will($this->returnValue($jointEntities)); - $joint->expects($this->at(1)) - ->method('delete') - ->with($existing[1]); - $joint->expects($this->at(2)) - ->method('delete') - ->with($existing[2]); + $assoc->replaceLinks($entity, $tagData, ['associated' => false]); + $this->assertSame($tagData, $entity->tags, 'Tags should match replaced objects'); + $this->assertFalse($entity->dirty('tags'), 'Should be clean'); - $options = ['foo' => 'bar']; - $assoc->expects($this->once()) - ->method('_saveTarget') - ->with($entity, [1 => $tags[1], 2 => $tags[2]], $options + ['associated' => false]) - ->will($this->returnCallback(function ($entity, $inserts) use ($tags) { - $this->assertSame([1 => $tags[1], 2 => $tags[2]], $inserts); - $entity->tags = $inserts; - return true; - })); + $fresh = $articles->get(1, ['contain' => 'Tags']); + $this->assertCount(3, $fresh->tags, 'Records should be in db'); - $assoc->replaceLinks($entity, $tags, $options + ['associated' => false]); - $this->assertSame($tags, $entity->tags); - $this->assertFalse($entity->dirty('tags')); + $this->assertNotEmpty($tags->get(2), 'Unlinked tag should still exist'); } /** * Tests that replaceLinks() will contain() the target table when * there are conditions present on the association. * + * In this case the replacement will fail because the association conditions + * hide the fixture data. + * * @return void */ public function testReplaceLinkWithConditions() { - $connection = ConnectionManager::get('test'); - $joint = $this->getMock( - '\Cake\ORM\Table', - ['find'], - [['alias' => 'ArticlesTags', 'connection' => $connection]] - ); - $config = [ - 'sourceTable' => $this->article, - 'targetTable' => $this->tag, - 'through' => $joint, - 'joinTable' => 'tags_articles', - 'conditions' => ['Tags.id' => 'blah'], - ]; - $assoc = $this->getMock( - '\Cake\ORM\Association\BelongsToMany', - ['_collectJointEntities'], - ['tags', $config] - ); - $assoc->junction(); + $joint = TableRegistry::get('SpecialTags'); + $articles = TableRegistry::get('Articles'); + $tags = TableRegistry::get('Tags'); - $query1 = $this->getMock( - '\Cake\ORM\Query', - ['where', 'andWhere', 'addDefaultTypes', 'contain'], - [$connection, $joint] - ); - $query1->expects($this->at(0)) - ->method('where') - ->will($this->returnSelf()); - $query1->expects($this->at(1)) - ->method('where') - ->will($this->returnSelf()); - $query1->expects($this->at(2)) - ->method('contain') - ->with('Tags') - ->will($this->returnSelf()); - $query1->expects($this->at(3)) - ->method('andWhere') - ->with($config['conditions']) - ->will($this->returnSelf()); - $query1->setResult(new \ArrayIterator([])); - - $joint->expects($this->at(0))->method('find') - ->with('all') - ->will($this->returnValue($query1)); - - $entity = new Entity(['id' => 1, 'test' => []]); - - $assoc->expects($this->once())->method('_collectJointEntities') - ->with($entity, []) - ->will($this->returnValue([])); + $assoc = $articles->belongsToMany('Tags', [ + 'sourceTable' => $articles, + 'targetTable' => $tags, + 'through' => $joint, + 'joinTable' => 'special_tags', + 'conditions' => ['SpecialTags.highlighted' => true] + ]); + $entity = $articles->get(1, ['contain' => 'Tags']); $assoc->replaceLinks($entity, [], ['associated' => false]); + $this->assertSame([], $entity->tags, 'Tags should match replaced objects'); + $this->assertFalse($entity->dirty('tags'), 'Should be clean'); + + $fresh = $articles->get(1, ['contain' => 'Tags']); + $this->assertCount(0, $fresh->tags, 'Association should be empty'); + + $jointCount = $joint->find()->where(['article_id' => 1])->count(); + $this->assertSame(1, $jointCount, 'Non matching joint record should remain.'); } /**