From: demeritcowboy Date: Fri, 21 Jul 2023 03:34:09 +0000 (-0400) Subject: add real-world example test X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=ae9c6b4287eac017a44650e36911934004099cad;p=civicrm-core.git add real-world example test --- diff --git a/tests/phpunit/api/v3/JobTest.php b/tests/phpunit/api/v3/JobTest.php index 2685c0e10c..19a1acca4a 100644 --- a/tests/phpunit/api/v3/JobTest.php +++ b/tests/phpunit/api/v3/JobTest.php @@ -36,6 +36,13 @@ class api_v3_JobTest extends CiviUnitTestCase { */ private $originalValues = []; + /** + * Make sure triggers are rebuilt even if test fails. We don't need to do it + * for every test, so use this to signal tearDown. + * @var bool + */ + private $rebuildTriggers = FALSE; + /** * Set up for tests. */ @@ -58,6 +65,12 @@ class api_v3_JobTest extends CiviUnitTestCase { */ public function tearDown(): void { $this->resetHooks(); + if ($this->rebuildTriggers) { + \Civi::service('sql_triggers')->rebuild(); + // not sure if this is necessary but clear it to be sure + CRM_Core_DAO::executeQuery('SET @CIVICRM_MERGE=NULL'); + $this->rebuildTriggers = FALSE; + } $this->quickCleanUpFinancialEntities(); $this->quickCleanup(['civicrm_contact', 'civicrm_address', 'civicrm_email', 'civicrm_relationship', 'civicrm_website', 'civicrm_phone', 'civicrm_job', 'civicrm_action_log', 'civicrm_action_schedule', 'civicrm_group', 'civicrm_group_contact'], TRUE); foreach ($this->originalValues as $entity => $entities) { @@ -1089,6 +1102,108 @@ class api_v3_JobTest extends CiviUnitTestCase { $this->assertEquals('', $mouse['custom_' . $customField['id']]); } + /** + * hook_civicrm_merge implementation for testBatchMergeCustomDataViewOnlyDateField + */ + public function hookMergeViewOnly($type, &$data, $mainId = NULL, $otherId = NULL, $tables = NULL) { + if ($mainId && $otherId) { + if ($type = 'sqls' && isset($tables)) { + // prevent DB trigger from forcing our view-only date field to CURRENT_TIMESTAMP + CRM_Core_DAO::executeQuery('SET @CIVICRM_MERGE=1'); + } + } + } + + /** + * Similar to testBatchMergeCustomDataViewOnlyField but with a hook and it's a date field. + * This is based on a real-world example and demonstrates one reason we're enforcing view-only custom fields get merged. + * There are two fields that go together, and it doesn't make sense to merge one but not the other, and the view-only date field is not easily recomputable. + */ + public function testBatchMergeCustomDataViewOnlyDateField(): void { + CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit my contact']; + + $customGroup = $this->customGroupCreate(); + $customGroup = $this->callAPISuccess('CustomGroup', 'getsingle', ['id' => $customGroup['id'], 'return' => ['id', 'table_name']]); + $customField = $this->customFieldCreate(['custom_group_id' => $customGroup['id']]); + $customField = $this->callAPISuccess('CustomField', 'getsingle', ['id' => $customField['id'], 'return' => ['id', 'column_name']]); + $customFieldDate = $this->customFieldCreate([ + 'custom_group_id' => $customGroup['id'], + 'label' => 'Custom Last Updated', + 'data_type' => 'Date', + 'html_type' => 'Select Date', + 'is_view' => 1, + // It seems like it creates db errors if we don't specify these? Don't feel like looking into that right now. + 'is_searchable' => 0, + 'date_format' => 'mm/dd/yy', + 'time_format' => 1, + 'default_value' => NULL, + ]); + $customFieldDate = $this->callAPISuccess('CustomField', 'getsingle', ['id' => $customFieldDate['id'], 'return' => ['id', 'column_name']]); + + $this->hookClass->setHook('civicrm_merge', [$this, 'hookMergeViewOnly']); + $this->hookClass->setHook('civicrm_triggerInfo', function(&$info, $tableName) use ($customGroup, $customField, $customFieldDate) { + // code styling is complaining so do it this way + $sqlinsert = << '') THEN + SET NEW.{$customFieldDate['column_name']} = CURRENT_TIMESTAMP; + END IF; + END IF; +ENDSQLINSERT; + $sqlupdate = << '' AND (NEW.{$customField['column_name']} <> OLD.{$customField['column_name']} OR OLD.{$customField['column_name']} IS NULL)) THEN + SET NEW.{$customFieldDate['column_name']} = CURRENT_TIMESTAMP; + END IF; + END IF; +ENDSQLUPDATE; + $info[] = [ + 'table' => $customGroup['table_name'], + 'when' => 'BEFORE', + 'event' => ['INSERT'], + 'sql' => $sqlinsert, + ]; + $info[] = [ + 'table' => $customGroup['table_name'], + 'when' => 'BEFORE', + 'event' => ['UPDATE'], + 'sql' => $sqlupdate, + ]; + }); + // let tearDown know about us to reset the triggers after + $this->rebuildTriggers = TRUE; + \Civi::service('sql_triggers')->rebuild(); + + // create first contact, without the (regular) custom field value. + $mouseParams = ['first_name' => 'Mickey', 'last_name' => 'Mouse', 'email' => 'tha_mouse@mouse.com']; + $mouseContactId = $this->individualCreate($mouseParams); + + // Check that the date field was NOT set + // See comment at bottom why this is important + $datevalue = $this->callAPISuccess('Contact', 'getsingle', ['id' => $mouseContactId, 'return' => ['custom_' . $customFieldDate['id']]]); + $datevalue = $datevalue['custom_' . $customFieldDate['id']]; + $this->assertEmpty($datevalue); + + // create second contact, with a value. + $duplicateId = $this->individualCreate(array_merge($mouseParams, ['custom_' . $customField['id'] => 'blah'])); + + // get the view-only field's current value for the 2nd contact which should have been set by trigger + $viewOnlyFieldValue = $this->callAPISuccess('Contact', 'getsingle', ['id' => $duplicateId, 'return' => ['custom_' . $customFieldDate['id']]]); + $viewOnlyFieldValue = $viewOnlyFieldValue['custom_' . $customFieldDate['id']]; + $this->assertNotEmpty($viewOnlyFieldValue); + + // Merge. Since the date field and regular field go together, we want those merged, and our hooks are set up so that the triggers won't update the date field during merge. + $result = $this->callAPISuccess('Job', 'process_batch_merge', ['check_permissions' => 0, 'mode' => 'safe']); + $this->assertCount(1, $result['values']['merged']); + + $mouse = $this->callAPISuccess('Contact', 'getsingle', ['id' => $mouseContactId, 'return' => ['custom_' . $customField['id'], 'custom_' . $customFieldDate['id']]]); + // check the regular field got merged just while we're here + $this->assertEquals('blah', $mouse['custom_' . $customField['id']]); + // now check the view-only field. It should be the one that was merged from the duplicate. + // Note that the original contact will not have a value for the custom date field because there was no corresponding regular custom field value, so we don't have to worry about a timing issue where both date fields happen to have the same timestamp. We've already checked above that both the original is blank and the duplicate has a nonempty value. + $this->assertEquals($viewOnlyFieldValue, $mouse['custom_' . $customFieldDate['id']]); + } + /** * Test the batch merge retains 0 as a valid custom field value. *