From ac4e1b936c98cc5745d8b7fc6aa7d63a965e009d Mon Sep 17 00:00:00 2001 From: demeritcowboy Date: Tue, 8 Aug 2023 15:54:20 -0400 Subject: [PATCH] wip --- .../Utils/Mail/EmailProcessorInboundTest.php | 162 +++++++++++++++++- .../CRM/Utils/Mail/data/inbound/test_hook.eml | 15 ++ 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/CRM/Utils/Mail/data/inbound/test_hook.eml diff --git a/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php b/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php index b48d49265a..adafef7b55 100644 --- a/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php +++ b/tests/phpunit/CRM/Utils/Mail/EmailProcessorInboundTest.php @@ -33,6 +33,8 @@ class CRM_Utils_Mail_EmailProcessorInboundTest extends CiviUnitTestCase { 'activity_targets' => 'to,cc,bcc', 'activity_assignees' => 'from', ])['id']; + $this->callAPISuccess('Tag', 'create', ['name' => 'FileToOrgAlways']); + $this->callAPISuccess('Tag', 'create', ['name' => 'FileToOrgCatchallForDomain']); } public function tearDown(): void { @@ -40,7 +42,16 @@ class CRM_Utils_Mail_EmailProcessorInboundTest extends CiviUnitTestCase { $this->callAPISuccess('MailSettings', 'delete', [ 'id' => $this->mailSettingsId, ]); - $this->quickCleanup(['civicrm_file', 'civicrm_entity_file']); + $this->quickCleanup([ + 'civicrm_file', + 'civicrm_entity_file', + 'civicrm_activity', + 'civicrm_activity_contact', + 'civicrm_entity_tag', + 'civicrm_tag', + 'civicrm_email', + 'civicrm_contact', + ]); parent::tearDown(); } @@ -124,4 +135,153 @@ class CRM_Utils_Mail_EmailProcessorInboundTest extends CiviUnitTestCase { Civi::settings()->set('max_attachments_backend', $currentBackendMax); } + /** + * hook implementation for testHookEmailProcessor + */ + public function hookImplForEmailProcessor($type, &$params, $mail, &$result, $action = NULL) { + if ($type !== 'activity') { + return; + } + // change the activity type depending on subject + if (strpos($mail->subject, 'for hooks') !== FALSE) { + $this->callAPISuccess('Activity', 'create', ['id' => $result['id'], 'activity_type_id' => 'Phone Call']); + } + } + + /** + * test hook_civicrm_emailProcessor + */ + public function testHookEmailProcessor() { + $this->hookClass->setHook('civicrm_emailProcessor', [$this, 'hookImplForEmailProcessor']); + + copy(__DIR__ . '/data/inbound/test_hook.eml', __DIR__ . '/data/mail/test_hook.eml'); + $this->callAPISuccess('Job', 'fetch_activities', []); + + // The activity type should be changed. + $activity = $this->callAPISuccess('Activity', 'getsingle', ['subject' => 'This is for hooks']); + $this->assertEquals( + CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Phone Call'), + $activity['activity_type_id'] + ); + + // Now repeat with a different subject, and the type should be the default. + copy(__DIR__ . '/data/inbound/test_non_cases_email.eml', __DIR__ . '/data/mail/test_non_cases_email.eml'); + $this->callAPISuccess('Job', 'fetch_activities', []); + $activity = $this->callAPISuccess('Activity', 'getsingle', ['subject' => 'Love letter']); + $this->assertEquals( + CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Inbound Email'), + $activity['activity_type_id'] + ); + } + + /** + * This hook is the example from the docs, which at one time was + * being used in production. + * If an org is specially tagged and has the same domain, then file + * on that org, otherwise if an individual had matched already, then use + * that, otherwise if there is an org specially tagged + * as a "catchall" for the domain, then use that. + * The use case is where you get emails from billing@acme.com, sales@acme.com, + * info@acme.com, do-not-reply@acme.com, the + * account-rep-of-the-week@acme.com, etc and these are really all the same + * org contact as far as you are concerned. However for some orgs you want it + * all filed on one contact no matter what, and for others you only want that + * as a fallback if it doesn't match an individual first. + */ + public function hookImplForEmailProcessorContact($email, $contactID, &$result) { + list($mailName, $mailDomain) = CRM_Utils_System::explode('@', $email, 2); + if (empty($mailDomain)) { + return; + } + $org = \Civi\Api4\Contact::get(FALSE) + ->addWhere('email_primary.email', 'LIKE', '%@' . $mailDomain) + ->addWhere('tags:name', 'IN', ['FileToOrgAlways']) + ->addWhere('contact_type:name', '=', 'Organization') + ->execute()->first(); + if ($org['id'] ?? NULL) { + $result = ['contactID' => $org['id'], 'action' => CRM_Utils_Mail_Incoming::EMAILPROCESSOR_OVERRIDE]; + return; + } + if ($contactID) { + return; + } + $org = \Civi\Api4\Contact::get(FALSE) + ->addWhere('email_primary.email', 'LIKE', '%@' . $mailDomain) + ->addWhere('tags:name', 'IN', ['FileToOrgCatchallForDomain']) + ->addWhere('contact_type:name', '=', 'Organization') + ->execute()->first(); + if ($org['id'] ?? NULL) { + $result = ['contactID' => $org['id'], 'action' => CRM_Utils_Mail_Incoming::EMAILPROCESSOR_OVERRIDE]; + return; + } + $result = ['action' => CRM_Utils_Mail_Incoming::EMAILPROCESSOR_CREATE_INDIVIDUAL]; + } + + /** + * test hook_civicrm_emailProcessorContact with catchall + */ + public function testHookEmailProcessorContactCatchall() { + $this->hookClass->setHook('civicrm_emailProcessorContact', [$this, 'hookImplForEmailProcessorContact']); + + // org with same domain as data fixture's email + $catchall_id = $this->organizationCreate(['organization_name' => 'Paradox Unlimited Ltd.', 'email' => 'info@paradox.biz']); + $tag = $this->callAPISuccess('Tag', 'getsingle', ['name' => 'FileToOrgCatchallForDomain', 'return' => ['id']]); + $this->callAPISuccess('EntityTag', 'create', ['entity_table' => 'civicrm_contact', 'entity_id' => $catchall_id, 'tag_id' => $tag['id']]); + + copy(__DIR__ . '/data/inbound/test_hook.eml', __DIR__ . '/data/mail/test_hook.eml'); + $this->callAPISuccess('Job', 'fetch_activities', []); + + // The source contact should be our catchall not the original From contact. + $activity = $this->callAPISuccess('Activity', 'getsingle', ['source_contact_id' => $catchall_id]); + $this->assertEquals('This is for hooks', $activity['subject']); + } + + /** + * test hook_civicrm_emailProcessorContact with catchall but matching individual + */ + public function testHookEmailProcessorContactCatchallWithMatch() { + $this->hookClass->setHook('civicrm_emailProcessorContact', [$this, 'hookImplForEmailProcessorContact']); + + // org with same domain as data fixture's email + $catchall_id = $this->organizationCreate(['organization_name' => 'Paradox Unlimited Ltd.', 'email' => 'info@paradox.biz']); + $tag = $this->callAPISuccess('Tag', 'getsingle', ['name' => 'FileToOrgCatchallForDomain', 'return' => ['id']]); + $this->callAPISuccess('EntityTag', 'create', ['entity_table' => 'civicrm_contact', 'entity_id' => $catchall_id, 'tag_id' => $tag['id']]); + + // individual with same email as the data fixture's email. + $contact_id = $this->individualCreate(['email' => 'billing@paradox.biz']); + + copy(__DIR__ . '/data/inbound/test_hook.eml', __DIR__ . '/data/mail/test_hook.eml'); + $this->callAPISuccess('Job', 'fetch_activities', []); + + // The source contact should be our individual not the catchall + $activity = $this->callAPISuccess('Activity', 'getsingle', ['source_contact_id' => $contact_id]); + $this->assertEquals('This is for hooks', $activity['subject']); + } + + /** + * test hook_civicrm_emailProcessorContact with Always and matching individual + * + * The difference from testHookEmailProcessorContactCatchallWithMatch is + * here the presence of the matching individual is irrelevant - it will always + * file on the org. + */ + public function testHookEmailProcessorContactAlwaysWithMatch() { + $this->hookClass->setHook('civicrm_emailProcessorContact', [$this, 'hookImplForEmailProcessorContact']); + + // org with same domain as data fixture's email + $catchall_id = $this->organizationCreate(['organization_name' => 'Paradox Unlimited Ltd.', 'email' => 'info@paradox.biz']); + $tag = $this->callAPISuccess('Tag', 'getsingle', ['name' => 'FileToOrgAlways', 'return' => ['id']]); + $this->callAPISuccess('EntityTag', 'create', ['entity_table' => 'civicrm_contact', 'entity_id' => $catchall_id, 'tag_id' => $tag['id']]); + + // individual with same email as the data fixture's email. + $contact_id = $this->individualCreate(['email' => 'billing@paradox.biz']); + + copy(__DIR__ . '/data/inbound/test_hook.eml', __DIR__ . '/data/mail/test_hook.eml'); + $this->callAPISuccess('Job', 'fetch_activities', []); + + // The source contact should be the org not the individual + $activity = $this->callAPISuccess('Activity', 'getsingle', ['source_contact_id' => $catchall_id]); + $this->assertEquals('This is for hooks', $activity['subject']); + } + } diff --git a/tests/phpunit/CRM/Utils/Mail/data/inbound/test_hook.eml b/tests/phpunit/CRM/Utils/Mail/data/inbound/test_hook.eml new file mode 100644 index 0000000000..9ba71817da --- /dev/null +++ b/tests/phpunit/CRM/Utils/Mail/data/inbound/test_hook.eml @@ -0,0 +1,15 @@ +Delivered-To: jjj@myorg.org +Received: by 10.2.13.84 with SMTP id 1234567890; + Tue, 08 Aug 2023 10:01:11 +0100 (CET) +Return-Path: <> +Message-ID: +Date: Tue, 08 Aug 2023 10:01:07 +0100 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline +From: billing@paradox.biz +To: jjj@myorg.org +Subject: This is for hooks + +Sample body for hook test. -- 2.25.1