From dba77ae8876d837fe9c9d1a256ad243598f6ce9b Mon Sep 17 00:00:00 2001 From: Camilo Rodriguez Date: Tue, 8 Aug 2017 21:45:48 +0000 Subject: [PATCH] CRM-20858: Add Failing Tests to Verify Merging of Custom Fields If a contact that has values stored in a custom group table is merged into a contact that doesn't have a record in that table, all values in the source target will be merged into the target contact, even if those values were not explicity selected to the merge operation. The merging operation updates all database references from the source contact to the target contact on all tables. When the target contact has a record on a custom group table, this update will fail, since the table's configuration enforces the entity_id (the reference to the contacts table) to be unique. However, when the target contact has no record on that custom group table, the source contact does have a record and the merge is performed, the original record will be updated to reference the target contact, and hence inherit ALL values of the source contact, even if none of the values were selected to be merged. Added two tests, the first one to verify that merging a contact with a record on a custom group table does not merge its values into a target contact with no record in the custom group table; the second one to check that if only some fields of a custom group table are selected to be merged, only those values are copied, leaving all others unset. --- tests/phpunit/CRM/Dedupe/MergerTest.php | 219 +++++++++++++++++++----- 1 file changed, 176 insertions(+), 43 deletions(-) mode change 100644 => 100755 tests/phpunit/CRM/Dedupe/MergerTest.php diff --git a/tests/phpunit/CRM/Dedupe/MergerTest.php b/tests/phpunit/CRM/Dedupe/MergerTest.php old mode 100644 new mode 100755 index 627e5f5265..2638e28537 --- a/tests/phpunit/CRM/Dedupe/MergerTest.php +++ b/tests/phpunit/CRM/Dedupe/MergerTest.php @@ -488,80 +488,213 @@ class CRM_Dedupe_MergerTest extends CiviUnitTestCase { * selecting/not selecting option to migrate data respectively */ public function testCustomDataOverwrite() { + // Create Custom Field + $createGroup = $this->setupCustomGroupForIndividual(); + $createField = $this->setupCustomField('Graduation', $createGroup); + $customFieldName = "custom_" . $createField['id']; + + // Contacts setup $this->setupMatchData(); $originalContactID = $this->contacts[0]['id']; $duplicateContactID1 = $this->contacts[1]['id']; // used as duplicate contact in 1st use-case $duplicateContactID2 = $this->contacts[2]['id']; // used as duplicate contact in 2nd use-case - // create custom set that extends Individual - $createGroup = $this->callAPISuccess('custom_group', 'create', array( - 'title' => 'Test_Group', - 'name' => 'test_group', - 'extends' => array('Individual'), - 'style' => 'Inline', - 'is_multiple' => FALSE, - 'is_active' => 1, - )); - // create custom field of HTML type 'Text' - $createField = $this->callAPISuccess('custom_field', 'create', array( - 'label' => 'Graduation', - 'data_type' => 'Alphanumeric', - 'html_type' => 'Text', - 'custom_group_id' => $createGroup['id'], - )); - $customFieldName = "custom_" . $createField['id']; // update the text custom field for original contact with value 'abc' $this->callAPISuccess('Contact', 'create', array( 'id' => $originalContactID, - $customFieldName => 'abc', + "{$customFieldName}" => 'abc', )); + $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName); + // update the text custom field for duplicate contact 1 with value 'def' $this->callAPISuccess('Contact', 'create', array( 'id' => $duplicateContactID1, - "custom_{$customFieldName}" => 'def', + "{$customFieldName}" => 'def', )); + $this->assertCustomFieldValue($duplicateContactID1, 'def', $customFieldName); + // update the text custom field for duplicate contact 2 with value 'ghi' $this->callAPISuccess('Contact', 'create', array( 'id' => $duplicateContactID2, - "custom_{$customFieldName}" => 'ghi', + "{$customFieldName}" => 'ghi', )); + $this->assertCustomFieldValue($duplicateContactID2, 'ghi', $customFieldName); /*** USE-CASE 1: DO NOT OVERWRITE CUSTOM FIELD VALUE **/ - $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID1); - $migrationData = array( - 'main_details' => $rowsElementsAndInfo['main_details'], - 'other_details' => $rowsElementsAndInfo['other_details'], - "move_{$customFieldName}" => NULL, - ); - // migrate data of duplicate contact - CRM_Dedupe_Merger::moveAllBelongings($originalContactID, $duplicateContactID1, $migrationData); - $data = $this->callAPISuccess('Contact', 'getsingle', array( - 'id' => $originalContactID, - 'return' => array($customFieldName), + $this->mergeContacts($originalContactID, $duplicateContactID1, array( + "move_{$customFieldName}" => null, )); - // ensure that the value is not overridden - $this->assertEquals('abc', $data[$customFieldName], 'Custom field value wasn\'t suppose to be overridden with duplicate contact'); + $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName); /*** USE-CASE 2: OVERWRITE CUSTOM FIELD VALUE **/ - $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID2); + $this->mergeContacts($originalContactID, $duplicateContactID2, array( + "move_{$customFieldName}" => 'ghi', + )); + $this->assertCustomFieldValue($originalContactID, 'ghi', $customFieldName); + + // cleanup created custom set + $this->callAPISuccess('CustomField', 'delete', array('id' => $createField['id'])); + $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); + } + + /** + * Verifies that when a contact with a custom field value is merged into a + * contact without a record int its corresponding custom group table, and none + * of the custom fields of that custom table are selected, the value is not + * merged in. + */ + public function testMigrationOfUnselectedCustomDataOnEmptyCustomRecord() { + // Create Custom Fields + $createGroup = $this->setupCustomGroupForIndividual(); + $customField1 = $this->setupCustomField('TestField', $createGroup); + + // Contacts setup + $this->setupMatchData(); + $originalContactID = $this->contacts[0]['id']; + $duplicateContactID = $this->contacts[1]['id']; + + // Update the text custom fields for duplicate contact + $this->callAPISuccess('Contact', 'create', array( + 'id' => $duplicateContactID, + "custom_{$customField1['id']}" => 'abc', + )); + $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}"); + + $this->mergeContacts($originalContactID, $duplicateContactID, array( + "move_custom_{$customField1['id']}" => null, + )); + $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}"); + + // cleanup created custom set + $this->callAPISuccess('CustomField', 'delete', array('id' => $customField1['id'])); + $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); + } + + /** + * Tests that if only part of the custom fields of a custom group are selected + * for a merge, only those values are merged, while all other fields of the + * custom group retain their original value, specifically for a contact with + * no records on the custom group table. + */ + public function testMigrationOfSomeCustomDataOnEmptyCustomRecord() { + // Create Custom Fields + $createGroup = $this->setupCustomGroupForIndividual(); + $customField1 = $this->setupCustomField('Test1', $createGroup); + $customField2 = $this->setupCustomField('Test2', $createGroup); + + // Contacts setup + $this->setupMatchData(); + $originalContactID = $this->contacts[0]['id']; + $duplicateContactID = $this->contacts[1]['id']; + + // Update the text custom fields for duplicate contact + $this->callAPISuccess('Contact', 'create', array( + 'id' => $duplicateContactID, + "custom_{$customField1['id']}" => 'abc', + "custom_{$customField2['id']}" => 'def', + )); + $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}"); + $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$customField2['id']}"); + + // Perform merge + $this->mergeContacts($originalContactID, $duplicateContactID, array( + "move_custom_{$customField1['id']}" => null, + "move_custom_{$customField2['id']}" => 'def', + )); + $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}"); + $this->assertCustomFieldValue($originalContactID, 'def', "custom_{$customField2['id']}"); + + // cleanup created custom set + $this->callAPISuccess('CustomField', 'delete', array('id' => $customField1['id'])); + $this->callAPISuccess('CustomField', 'delete', array('id' => $customField2['id'])); + $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); + } + + /** + * Calls merge method on given contacts, with values given in $params array. + * + * @param $originalContactID + * ID of target contact + * @param $duplicateContactID + * ID of contact to be merged + * @param $params + * Array of fields to be merged from source into target contact, of the form + * ['move_' => ] + */ + private function mergeContacts($originalContactID, $duplicateContactID, $params) { + $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID); + $migrationData = array( 'main_details' => $rowsElementsAndInfo['main_details'], 'other_details' => $rowsElementsAndInfo['other_details'], - "move_{$customFieldName}" => 'ghi', ); - // migrate data of duplicate contact - CRM_Dedupe_Merger::moveAllBelongings($originalContactID, $duplicateContactID2, $migrationData); + + // Migrate data of duplicate contact + CRM_Dedupe_Merger::moveAllBelongings($originalContactID, $duplicateContactID, array_merge($migrationData, $params)); + } + + /** + * Checks if the expected value for the given field corresponds to what is + * stored in the database for the given contact ID. + * + * @param $contactID + * @param $expectedValue + * @param $customFieldName + */ + private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) { $data = $this->callAPISuccess('Contact', 'getsingle', array( - 'id' => $originalContactID, + 'id' => $contactID, 'return' => array($customFieldName), )); - // ensure that value is overridden - $this->assertEquals('ghi', $data[$customFieldName], 'Custom field value was suppose to be overridden with duplicate contact'); - // cleanup created custom set - $this->callAPISuccess('CustomField', 'delete', array('id' => $createField['id'])); - $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); + $this->assertEquals($expectedValue, $data[$customFieldName], "Custom field value was supposed to be '{$expectedValue}', '{$data[$customFieldName]}' found."); + } + + /** + * Creates a custom group to run tests on contacts that are individuals. + * + * @return array + * Data for the created custom group record + */ + private function setupCustomGroupForIndividual() { + $customGroup = $this->callAPISuccess('custom_group', 'get', array( + 'name' => 'test_group', + )); + + if ($customGroup['count'] > 0) { + $this->callAPISuccess('CustomGroup', 'delete', array('id' => $customGroup['id'])); + } + + $customGroup = $this->callAPISuccess('custom_group', 'create', array( + 'title' => 'Test_Group', + 'name' => 'test_group', + 'extends' => array('Individual'), + 'style' => 'Inline', + 'is_multiple' => FALSE, + 'is_active' => 1, + )); + + return $customGroup; + } + + /** + * Creates a custom field on the provided custom group with the given field + * label. + * + * @param $fieldLabel + * @param $createGroup + * + * @return array + * Data for the created custom field record + */ + private function setupCustomField($fieldLabel, $createGroup) { + return $this->callAPISuccess('custom_field', 'create', array( + 'label' => $fieldLabel, + 'data_type' => 'Alphanumeric', + 'html_type' => 'Text', + 'custom_group_id' => $createGroup['id'], + )); } /** -- 2.25.1