Issues (3627)

Tests/Integration/SalesforceIntegrationTest.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2016 Mautic Contributors. All rights reserved
5
 * @author      Mautic, Inc.
6
 *
7
 * @link        https://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace MauticPlugin\MauticCrmBundle\Tests\Integration;
13
14
use Mautic\CoreBundle\Entity\AuditLogRepository;
15
use Mautic\LeadBundle\Entity\Company;
16
use Mautic\LeadBundle\Entity\Lead;
17
use Mautic\PluginBundle\Entity\Integration;
18
use Mautic\PluginBundle\Entity\IntegrationEntity;
19
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
20
use Mautic\PluginBundle\Event\PluginIntegrationKeyEvent;
21
use Mautic\PluginBundle\Exception\ApiErrorException;
22
use Mautic\PluginBundle\Model\IntegrationEntityModel;
23
use Mautic\PluginBundle\Tests\Integration\AbstractIntegrationTestCase;
24
use MauticPlugin\MauticCrmBundle\Integration\SalesforceIntegration;
25
use PHPUnit\Framework\MockObject\MockObject;
26
use ReflectionClass;
27
28
class SalesforceIntegrationTest extends AbstractIntegrationTestCase
29
{
30
    const SC_MULTIPLE_SF_LEADS        = 'multiple_sf_leads';
31
    const SC_MULTIPLE_SF_CONTACTS     = 'multiple_sf_contacts';
32
    const SC_CONVERTED_SF_LEAD        = 'converted_sf_lead';
33
    const SC_EMAIL_WITH_APOSTROPHE    = 'email_with_apostrophe';
34
    const SC_MULTIPLE_MAUTIC_CONTACTS = 'multiple_mautic_contacts';
35
36
    /**
37
     * @var array
38
     */
39
    protected $maxInvocations = [];
40
41
    /**
42
     * @var string|null
43
     */
44
    protected $specialSfCase;
45
46
    /**
47
     * @var array
48
     */
49
    protected $persistedIntegrationEntities = [];
50
51
    /**
52
     * @var array
53
     */
54
    protected $returnedSfEntities = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $mauticContacts = [
60
        'Contact' => [],
61
        'Lead'    => [],
62
    ];
63
64
    /**
65
     * @var array
66
     */
67
    protected $sfObjects = [
68
        'Lead',
69
        'Contact',
70
        'company',
71
    ];
72
73
    /**
74
     * @var array
75
     */
76
    protected $sfMockMethods = [
77
        'makeRequest',
78
    ];
79
80
    /**
81
     * @var array
82
     */
83
    protected $sfMockResetMethods = [
84
        'makeRequest',
85
    ];
86
87
    /**
88
     * @var array
89
     */
90
    protected $sfMockResetObjects = [
91
        'Lead',
92
        'Contact',
93
        'company',
94
    ];
95
96
    /**
97
     * @var int
98
     */
99
    protected $idCounter = 1;
100
101
    /**
102
     * @var array
103
     */
104
    protected $leadsUpdatedCounter = [
105
        'Lead'    => 0,
106
        'Contact' => 0,
107
    ];
108
109
    /**
110
     * @var int
111
     */
112
    protected $leadsCreatedCounter = 0;
113
114
    protected function setUp(): void
115
    {
116
        parent::setUp();
117
118
        defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
119
    }
120
121
    /**
122
     * Reset.
123
     */
124
    public function tearDown(): void
125
    {
126
        $this->returnedSfEntities           = [];
127
        $this->persistedIntegrationEntities = [];
128
        $this->sfMockMethods                = $this->sfMockResetMethods;
129
        $this->sfObjects                    = $this->sfMockResetObjects;
130
        $this->specialSfCase                = null;
131
        $this->idCounter                    = 1;
132
        $this->leadsCreatedCounter          = 0;
133
        $this->leadsUpdatedCounter          = [
134
            'Lead'    => 0,
135
            'Contact' => 0,
136
        ];
137
        $this->mauticContacts = [];
138
    }
139
140
    public function testPushLeadsUpdateAndCreateCorrectNumbers()
141
    {
142
        $sf    = $this->getSalesforceIntegration();
143
        $stats = $sf->pushLeads();
144
145
        $this->assertCount(400, $this->getPersistedIntegrationEntities());
146
        $this->assertEquals(300, $stats[0], var_export($stats, true)); // update
147
        $this->assertEquals(100, $stats[1], var_export($stats, true)); // create
148
    }
149
150
    public function testThatMultipleSfLeadsReturnedAreUpdatedButOnlyOneIntegrationRecordIsCreated()
151
    {
152
        $this->specialSfCase = self::SC_MULTIPLE_SF_LEADS;
153
        $sf                  = $this->getSalesforceIntegration(2, 0, 2, 0, 'Lead');
154
        $sf->pushLeads();
155
156
        // Validate there are only two integration entities (two contacts with same email)
157
        $integrationEntities = $this->getPersistedIntegrationEntities();
158
        $this->assertCount(2, $integrationEntities);
159
160
        // Validate that there were 4 found entries (two duplicate leads)
161
        $sfEntities = $this->getReturnedSfEntities();
162
        $this->assertCount(4, $sfEntities);
163
    }
164
165
    public function testThatMultipleSfContactsReturnedAreUpdatedButOnlyOneIntegrationRecordIsCreated()
166
    {
167
        $this->specialSfCase = self::SC_MULTIPLE_SF_CONTACTS;
168
        $sf                  = $this->getSalesforceIntegration(2, 0, 0, 2, 'Contact');
169
        $sf->pushLeads();
170
171
        // Validate there are only two integration entities (two contacts with same email)
172
        $integrationEntities = $this->getPersistedIntegrationEntities();
173
        $this->assertEquals(2, count($integrationEntities));
174
175
        // Validate that there were 4 found entries (two duplciate leads)
176
        $sfEntities = $this->getReturnedSfEntities();
177
        $this->assertEquals(4, count($sfEntities));
178
    }
179
180
    public function testThatLeadsAreOnlyCreatedIfEnabled()
181
    {
182
        $this->sfObjects     = ['Contact'];
183
        $this->sfMockMethods = ['makeRequest', 'findLeadsToCreate', 'getMauticContactsToCreate'];
184
185
        $sf = $this->getSalesforceIntegration();
186
        $sf->expects($this->never())
187
            ->method('findLeadsToCreate');
188
189
        $sf->expects($this->never())
190
            ->method('getMauticContactsToCreate');
191
192
        $sf->pushLeads();
193
    }
194
195
    public function testThatLeadsAreOnlyCreatedIfLimitIsAppropriate()
196
    {
197
        $this->sfMockMethods = ['makeRequest', 'getMauticContactsToCreate', 'getMauticContactsToUpdate', 'getSalesforceSyncLimit'];
198
199
        $sf      = $this->getSalesforceIntegration();
200
        $counter = 0;
201
        $sf->expects($this->exactly(2))
202
            ->method('getMauticContactsToUpdate')
203
            ->will(
204
                $this->returnCallback(
205
                    function () use (&$counter) {
206
                        ++$counter;
207
208
                        return true;
209
                    }
210
                )
211
            );
212
213
        $sf->method('getSalesforceSyncLimit')
214
            ->willReturn(50);
215
216
        $sf->expects($this->exactly(1))
217
            ->method('getMauticContactsToCreate');
218
219
        $sf->pushLeads();
220
    }
221
222
    public function testThatLeadsAreNotCreatedIfCountIsLessThanLimit()
223
    {
224
        $this->sfMockMethods = ['makeRequest', 'getMauticContactsToCreate', 'getMauticContactsToUpdate', 'getSalesforceSyncLimit'];
225
226
        $sf      = $this->getSalesforceIntegration();
227
        $counter = 0;
228
        $sf->expects($this->exactly(2))
229
            ->method('getMauticContactsToUpdate')
230
            ->will(
231
                $this->returnCallback(
232
                    function () use (&$counter) {
233
                        ++$counter;
234
235
                        return true;
236
                    }
237
                )
238
            );
239
240
        $counter = 0;
241
        $sf->method('getSalesforceSyncLimit')
242
            ->will(
243
                $this->returnCallback(
244
                    function () use (&$counter) {
245
                        ++$counter;
246
247
                        return (1 === $counter) ? 100 : 0;
248
                    }
249
                )
250
            );
251
252
        $sf->expects($this->never())
253
            ->method('getMauticContactsToCreate');
254
255
        $sf->pushLeads();
256
    }
257
258
    public function testLastSyncDate()
259
    {
260
        $class          = new \ReflectionClass(SalesforceIntegration::class);
261
        $lastSyncMethod = $class->getMethod('getLastSyncDate');
262
        $lastSyncMethod->setAccessible(true);
263
264
        $sf = $this->getSalesforceIntegration();
265
266
        // should be a DateTime
267
        $lastSync = $lastSyncMethod->invokeArgs($sf, []);
268
        $this->assertTrue($lastSync instanceof \DateTime);
269
270
        // Set the override set by fetch command
271
        define('MAUTIC_DATE_MODIFIED_OVERRIDE', time());
272
273
        /** @var \DateTime $lastSync */
274
        $lastSync = $lastSyncMethod->invokeArgs($sf, []);
275
276
        // should be teh same as MAUTIC_DATE_MODIFIED_OVERRIDE override
277
        $this->assertTrue($lastSync instanceof \DateTime);
278
        $this->assertEquals(MAUTIC_DATE_MODIFIED_OVERRIDE, $lastSync->format('U'));
279
280
        $lead          = new Lead();
281
        $reflectedLead = new \ReflectionObject($lead);
282
        $reflectedId   = $reflectedLead->getProperty('id');
283
        $reflectedId->setAccessible(true);
284
        $reflectedId->setValue($lead, 1);
285
286
        $modified = new \DateTime('-15 minutes');
287
        $lead->setDateModified($modified);
288
        // Set it twice to get an original and updated datetime
289
        $now = new \DateTime();
290
        $lead->setDateModified($now);
291
292
        $params = [
293
            'start' => $modified->format('c'),
294
        ];
295
296
        // Should be null due to the contact was updated since last sync
297
        $lastSync = $lastSyncMethod->invokeArgs($sf, [$lead, $params, false]);
298
        $this->assertNull($lastSync);
299
300
        // Should be a DateTime object
301
        $lead     = new Lead();
302
        $lastSync = $lastSyncMethod->invokeArgs($sf, [$lead, $params]);
303
        $this->assertTrue($lastSync instanceof \DateTime);
304
    }
305
306
    public function testLeadsAreNotCreatedInSfIfFoundToAlreadyExistAsContacts()
307
    {
308
        $this->sfObjects     = ['Lead', 'Contact'];
309
        $this->sfMockMethods = ['makeRequest', 'getSalesforceObjectsByEmails', 'prepareMauticContactsToCreate'];
310
        $sf                  = $this->getSalesforceIntegration(0, 2);
311
312
        /*
313
         * This forces the integration to think the contact exists in SF,
314
         * and removes those emails from the array for creation.
315
         */
316
        $sf->method('getSalesforceObjectsByEmails')
317
            ->willReturnCallback(
318
                function () {
319
                    $args = func_get_args();
320
                    $emails = array_column($args[1], 'email');
321
322
                    return $this->getSalesforceObjects($emails, 0, 1);
323
                }
324
            );
325
326
        $sf->expects($this->never())
327
            ->method('prepareMauticContactsToCreate');
328
329
        $sf->pushLeads();
330
    }
331
332
    public function testLeadsAreNotCreatedInSfIfFoundToAlreadyExistAsLeads()
333
    {
334
        $this->sfObjects     = ['Lead'];
335
        $this->sfMockMethods = ['makeRequest', 'getSalesforceObjectsByEmails', 'prepareMauticContactsToCreate'];
336
        $sf                  = $this->getSalesforceIntegration(0, 1);
337
338
        /*
339
         * This forces the integration to think the contact exists in SF,
340
         * and removes those emails from the array for creation.
341
         */
342
        $sf->method('getSalesforceObjectsByEmails')
343
            ->willReturnCallback(
344
                function () {
345
                    $args = func_get_args();
346
                    $emails = array_column($args[1], 'email');
347
348
                    return $this->getSalesforceObjects($emails, 0, 1);
349
                }
350
            );
351
352
        $sf->expects($this->never())
353
            ->method('prepareMauticContactsToCreate');
354
355
        $sf->pushLeads();
356
    }
357
358
    public function testExceptionIsThrownIfSfReturnsErrorOnEmailLookup()
359
    {
360
        $this->sfObjects     = ['Lead'];
361
        $this->sfMockMethods = ['makeRequest', 'getSalesforceObjectsByEmails'];
362
        $sf                  = $this->getSalesforceIntegration();
363
364
        $sf->method('getSalesforceObjectsByEmails')
365
            ->willReturn('Some Error');
366
367
        $this->expectException(ApiErrorException::class);
368
369
        $sf->pushLeads();
370
    }
371
372
    public function testGetCampaigns()
373
    {
374
        $this->sfObjects     = ['Contact'];
375
        $this->sfMockMethods = ['makeRequest'];
376
        $sf                  = $this->getSalesforceIntegration();
377
378
        $sf->expects($this->once())
379
            ->method('makeRequest')
380
            ->with(
381
                'https://sftest.com/services/data/v34.0/query',
382
                [
383
                    'q' => 'Select Id, Name from Campaign where isDeleted = false',
384
                ]
385
            );
386
387
        $sf->getCampaigns();
388
    }
389
390
    public function testGetCampaignMembers()
391
    {
392
        $this->sfObjects     = ['Contact'];
393
        $this->sfMockMethods = ['makeRequest'];
394
        $sf                  = $this->getSalesforceIntegration();
395
396
        $sf->expects($this->once())
397
            ->method('makeRequest')
398
            ->with(
399
                'https://sftest.com/services/data/v34.0/query',
400
                [
401
                    'q' => "Select CampaignId, ContactId, LeadId, isDeleted from CampaignMember where CampaignId = '1'",
402
                ]
403
            );
404
405
        $sf->getCampaignMembers(1);
406
    }
407
408
    public function testGetCampaignMemberStatus()
409
    {
410
        $this->sfObjects     = ['Contact'];
411
        $this->sfMockMethods = ['makeRequest'];
412
        $sf                  = $this->getSalesforceIntegration();
413
414
        $sf->expects($this->once())
415
            ->method('makeRequest')
416
            ->with(
417
                'https://sftest.com/services/data/v34.0/query',
418
                [
419
                    'q' => "Select Id, Label from CampaignMemberStatus where isDeleted = false and CampaignId='1'",
420
                ]
421
            );
422
423
        $sf->getCampaignMemberStatus(1);
424
    }
425
426
    public function testPushToCampaign()
427
    {
428
        $this->sfObjects     = ['Contact'];
429
        $this->sfMockMethods = ['makeRequest'];
430
        $sf                  = $this->getSalesforceIntegration();
431
432
        $lead = new Lead();
433
434
        $lead->setFirstname('Lead1');
435
        $lead->setEmail('[email protected]');
436
        $lead->setId(1);
437
438
        $sf->expects($this->any())
439
            ->method('makeRequest')
440
            ->willReturnCallback(
441
                function () {
442
                    $args = func_get_args();
443
444
                    // Checking for campaign members should return empty array for testing purposes
445
                    if (false !== strpos($args[0], '/query') && false !== strpos($args[1]['q'], 'CampaignMember')) {
446
                        return [];
447
                    }
448
449
                    if (false !== strpos($args[0], '/composite')) {
450
                        $this->assertSame(
451
                            '1-CampaignMemberNew-null-1',
452
                            $args[1]['compositeRequest'][0]['referenceId'],
453
                            'The composite request when pushing a campaign member should contain the correct referenceId.'
454
                        );
455
456
                        return true;
457
                    }
458
                }
459
            );
460
461
        $sf->pushLeadToCampaign($lead, 1, 'Active', ['Lead' => [1]]);
462
    }
463
464
    public function testPushCompany()
465
    {
466
        $this->sfObjects     = ['Account'];
467
        $this->sfMockMethods = ['makeRequest'];
468
        $sf                  = $this->getSalesforceIntegration();
469
470
        $company = new Company();
471
472
        $company->setName('MyCompanyName');
473
474
        $sf->expects($this->any())
475
            ->method('makeRequest')
476
            ->willReturnCallback(
477
                function () {
478
                    $args = func_get_args();
479
480
                    // Checking for campaign members should return empty array for testing purposes
481
                    if (false !== strpos($args[0], '/query') && false !== strpos($args[1]['q'], 'Account')) {
482
                        return [];
483
                    }
484
485
                    if (false !== strpos($args[0], '/composite')) {
486
                        $this->assertSame(
487
                            '1-Account-null-1',
488
                            $args[1]['compositeRequest'][0]['referenceId'],
489
                            'The composite request when pushing an account should contain the correct referenceId.'
490
                        );
491
492
                        return true;
493
                    }
494
                }
495
            );
496
497
        $this->assertFalse($sf->pushCompany($company));
498
    }
499
500
    public function testExportingContactActivity()
501
    {
502
        $this->sfObjects     = ['Contact'];
503
        $this->sfMockMethods = ['makeRequest', 'getSalesforceObjectsByEmails', 'isAuthorized', 'getFetchQuery', 'getLeadData'];
504
        $sf                  = $this->getSalesforceIntegration(2, 2);
505
506
        $sf->expects($this->once())
507
            ->method('isAuthorized')
508
            ->willReturn(true);
509
510
        $sf->expects($this->once())
511
            ->method('getFetchQuery')
512
            ->with([])
513
            ->willReturnCallback(
514
                function () {
515
                    return [
516
                        'start' => '-1 week',
517
                        'end'   => 'now',
518
                    ];
519
                }
520
            );
521
522
        $this->setMaxInvocations('getIntegrationsEntityId', 1);
523
524
        $sf->expects($this->once())
525
            ->method('getLeadData')
526
            ->willReturnCallback(
527
                function () {
528
                    $leadIds = func_get_arg(2);
529
                    $data = [];
530
531
                    foreach ($leadIds as $i => $id) {
532
                        ++$i;
533
534
                        $data[$id] = [
535
                            'records' => [
536
                                [
537
                                    'eventType'   => 'email',
538
                                    'name'        => 'Email Name',
539
                                    'description' => 'Email sent',
540
                                    'dateAdded'   => new \DateTime(),
541
                                    'id'          => 'pointChange'.$i,
542
                                ],
543
                            ],
544
                        ];
545
                    }
546
547
                    return $data;
548
                }
549
            );
550
551
        /*
552
         * Ensures that makeRequest is called with the mautic_timeline__c endpoint.
553
         * If it is, then we've successfully exported contact activity.
554
         */
555
        $sf->expects($this->exactly(1))
556
            ->method('makeRequest')
557
            ->with('https://sftest.com/services/data/v38.0/composite/')
558
            ->willReturnCallback(
559
                function () {
560
                    return $this->getSalesforceCompositeResponse(func_get_arg(1));
561
                }
562
            );
563
564
        $sf->pushLeadActivity();
565
    }
566
567
    public function testMauticContactTimelineLinkPopulatedsPayload()
568
    {
569
        $this->sfObjects     = ['Contact'];
570
        $this->sfMockMethods = ['makeRequest', 'getSalesforceObjectsByEmails'];
571
        $sf                  = $this->getSalesforceIntegration(2, 2);
572
573
        /*
574
         * When checking if contacts need to be created, the mauticContactTimelineLink
575
         * has been populated at this point. If populated here, the test passes.
576
         * We return the salesforce objects so as not to throw an error.
577
         */
578
        $sf->method('getSalesforceObjectsByEmails')
579
            ->willReturnCallback(
580
                function () {
581
                    $args = func_get_args();
582
                    $emails = array_column($args[1], 'email');
583
584
                    return $this->getSalesforceObjects($emails, 0, 1);
585
                }
586
            );
587
588
        /*
589
         * With the given SF integration setup, the `getSalesforceObjectsByEmails` method
590
         * will be called once, and have the given parameters, which contains the contact
591
         * timeline link.
592
         */
593
        $sf->expects($this->once())
594
            ->method('getSalesforceObjectsByEmails')
595
            ->with(
596
                'Contact',
597
                [
598
                    '[email protected]' => [
599
                        'integration_entity_id'             => 'SF1',
600
                        'integration_entity'                => 'Contact',
601
                        'id'                                => 1,
602
                        'internal_entity_id'                => 1,
603
                        'firstname'                         => 'Contact1',
604
                        'lastname'                          => 'Contact1',
605
                        'company'                           => 'Contact1',
606
                        'email'                             => '[email protected]',
607
                        'mauticContactTimelineLink'         => 'mautic_plugin_timeline_view',
608
                        'mauticContactIsContactableByEmail' => 1,
609
                        'mauticContactId'                   => 1,
610
                    ],
611
                    '[email protected]' => [
612
                        'integration_entity_id'             => 'SF2',
613
                        'integration_entity'                => 'Contact',
614
                        'id'                                => 2,
615
                        'internal_entity_id'                => 2,
616
                        'firstname'                         => 'Contact2',
617
                        'lastname'                          => 'Contact2',
618
                        'company'                           => 'Contact2',
619
                        'email'                             => '[email protected]',
620
                        'mauticContactTimelineLink'         => 'mautic_plugin_timeline_view',
621
                        'mauticContactIsContactableByEmail' => 1,
622
                        'mauticContactId'                   => 2,
623
                    ],
624
                ],
625
                'FirstName,LastName,Email'
626
            );
627
628
        $sf->pushLeads();
629
    }
630
631
    public function testUpdateDncBySfDate()
632
    {
633
        $this->sfMockMethods = ['makeRequest', 'updateDncByDate', 'getDncHistory'];
634
635
        $objects = ['Contact', 'Lead'];
636
        foreach ($objects as $object) {
637
            $mappedData = [
638
                '[email protected]' => [
639
                    'integration_entity_id'             => 'SF1',
640
                    'integration_entity'                => $object,
641
                    'id'                                => 1,
642
                    'internal_entity_id'                => 1,
643
                    'firstname'                         => 'Contact1',
644
                    'lastname'                          => 'Contact1',
645
                    'company'                           => 'Contact1',
646
                    'email'                             => '[email protected]',
647
                    'mauticContactTimelineLink'         => 'mautic_plugin_timeline_view',
648
                    'mauticContactIsContactableByEmail' => 1,
649
                ],
650
                '[email protected]' => [
651
                    'integration_entity_id'             => 'SF2',
652
                    'integration_entity'                => $object,
653
                    'id'                                => 2,
654
                    'internal_entity_id'                => 2,
655
                    'firstname'                         => 'Contact2',
656
                    'lastname'                          => 'Contact2',
657
                    'company'                           => 'Contact2',
658
                    'email'                             => '[email protected]',
659
                    'mauticContactTimelineLink'         => 'mautic_plugin_timeline_view',
660
                    'mauticContactIsContactableByEmail' => 0,
661
                ],
662
            ];
663
664
            $sf = $this->getSalesforceIntegration(2, 2);
665
            $sf->expects($this->any())->method('updateDncByDate')->willReturn(true);
666
            $sf->expects($this->any())
667
                ->method('getDncHistory')
668
                ->willReturn(
669
                    $this->getSalesforceDNCHistory($object, 'SF')
670
            );
671
            $sf->pushLeadDoNotContactByDate('email', $mappedData, $object, ['start' => '2017-10-16 13:00:00.000000']);
672
673
            foreach ($mappedData as $assertion) {
674
                $this->assertArrayHasKey('mauticContactIsContactableByEmail', $assertion);
675
            }
676
        }
677
    }
678
679
    public function testUpdateDncByMauticDate()
680
    {
681
        $this->sfMockMethods = ['makeRequest', 'updateDncByDate', 'getDncHistory'];
682
683
        $objects = ['Contact', 'Lead'];
684
        foreach ($objects as $object) {
685
            $mappedData = [
686
                '[email protected]' => [
687
                    'integration_entity_id'             => 'SF1',
688
                    'integration_entity'                => $object,
689
                    'id'                                => 1,
690
                    'internal_entity_id'                => 1,
691
                    'firstname'                         => 'Contact1',
692
                    'lastname'                          => 'Contact1',
693
                    'company'                           => 'Contact1',
694
                    'email'                             => '[email protected]',
695
                    'mauticContactTimelineLink'         => 'mautic_plugin_timeline_view',
696
                    'mauticContactIsContactableByEmail' => 1,
697
                ],
698
            ];
699
700
            $sf = $this->getSalesforceIntegration(2, 2);
701
            $sf->expects($this->any())->method('updateDncByDate')->willReturn(true);
702
            $sf->expects($this->any())->method('getDncHistory')->willReturn($this->getSalesforceDNCHistory($object, 'Mautic'));
703
704
            $sf->pushLeadDoNotContactByDate('email', $mappedData, $object, ['start' => '2017-10-15T10:00:00.000000']);
705
            foreach ($mappedData as $assertion) {
706
                $this->assertArrayNotHasKey('mauticContactIsContactableByEmail', $assertion);
707
            }
708
        }
709
    }
710
711
    /**
712
     * @param string $name
713
     * @param int    $max
714
     *
715
     * @return $this
716
     */
717
    protected function setMaxInvocations($name, $max)
718
    {
719
        $this->maxInvocations[$name] = $max;
720
721
        return $this;
722
    }
723
724
    /**
725
     * @param $name
726
     *
727
     * @return int
728
     */
729
    protected function getMaxInvocations($name)
730
    {
731
        if (isset($this->maxInvocations[$name])) {
732
            return $this->maxInvocations[$name];
733
        }
734
735
        return 1;
736
    }
737
738
    protected function setMocks()
739
    {
740
        $integrationEntityRepository = $this->getMockBuilder(IntegrationEntityRepository::class)
741
            ->disableOriginalConstructor()
742
            ->getMock();
743
744
        // we need insight into the entities persisted
745
        $integrationEntityRepository->method('saveEntities')
746
            ->willReturnCallback(
747
                function () {
748
                    $this->persistedIntegrationEntities = array_merge($this->persistedIntegrationEntities, func_get_arg(0));
749
                }
750
            );
751
752
        $integrationEntityRepository
753
            ->expects($spy = $this->any())
754
            ->method('getIntegrationsEntityId')
755
            ->willReturnCallback(
756
                function () use ($spy) {
757
                    // WARNING: this is using a PHPUnit undocumented workaround:
758
                    // https://github.com/sebastianbergmann/phpunit/issues/3888
759
                    $spyParentProperties = self::getParentPrivateProperties($spy);
760
                    $invocations = $spyParentProperties['invocations'];
761
762
                    if (count($invocations) > $this->getMaxInvocations('getIntegrationsEntityId')) {
763
                        return null;
764
                    }
765
766
                    // Just return some bogus entities for testing
767
                    return $this->getLeadsToUpdate('Lead', 2, 2, 'Lead')['Lead'];
768
                }
769
            );
770
        $auditLogRepo = $this->getMockBuilder(AuditLogRepository::class)
771
            ->disableOriginalConstructor()
772
            ->getMock();
773
774
        $auditLogRepo
775
            ->expects($this->any())
776
            ->method('getAuditLogsForLeads')
777
            ->willReturn(
778
                [
779
                    [
780
                        'userName' => 'Salesforce',
781
                        'userId'   => 0,
782
                        'bundle'   => 'lead',
783
                        'object'   => 'lead',
784
                        'objectId' => 1,
785
                        'action'   => 'update',
786
                        'details'  => [
787
                                'dnc_channel_status' => [
788
                                        'email' => [
789
                                                'reason'   => 3,
790
                                                'comments' => 'Set by Salesforce',
791
                                            ],
792
                                    ],
793
                                'dnc_status' => [
794
                                        'manual',
795
                                        'Set by Salesforce',
796
                                    ],
797
                            ],
798
                        'dateAdded' => new \DateTime('2017-10-16 15:00:36.000000', new \DateTimeZone('UTC')),
799
                        'ipAddress' => '127.0.0.1',
800
                    ],
801
                ]
802
            );
803
804
        $this->em->method('getRepository')
805
            ->willReturnMap(
806
                [
807
                    ['MauticPluginBundle:IntegrationEntity', $integrationEntityRepository],
808
                    ['MauticCoreBundle:AuditLog', $auditLogRepo],
809
                ]
810
            );
811
812
        $this->em->method('getReference')
813
            ->willReturnCallback(
814
                function () {
815
                    switch (func_get_arg(0)) {
816
                        case 'MauticPluginBundle:IntegrationEntity':
817
                            return new IntegrationEntity();
818
                    }
819
                }
820
            );
821
822
        $this->router->method('generate')
823
            ->willReturnArgument(0);
824
825
        $this->leadModel->method('getEntity')
826
            ->willReturn(new Lead());
827
828
        $this->companyModel->method('getEntity')
829
            ->willReturn(new Company());
830
        $this->companyModel->method('getEntities')
831
            ->willReturn([]);
832
833
        $leadFields = [
834
            'Id__Lead' => [
835
                    'type'        => 'string',
836
                    'label'       => 'Lead-Lead ID',
837
                    'required'    => false,
838
                    'group'       => 'Lead',
839
                    'optionLabel' => 'Lead ID',
840
                ],
841
            'LastName__Lead' => [
842
                    'type'        => 'string',
843
                    'label'       => 'Lead-Last Name',
844
                    'required'    => true,
845
                    'group'       => 'Lead',
846
                    'optionLabel' => 'Last Name',
847
                ],
848
            'FirstName__Lead' => [
849
                    'type'        => 'string',
850
                    'label'       => 'Lead-First Name',
851
                    'required'    => false,
852
                    'group'       => 'Lead',
853
                    'optionLabel' => 'First Name',
854
                ],
855
            'Company__Lead' => [
856
                    'type'        => 'string',
857
                    'label'       => 'Lead-Company',
858
                    'required'    => true,
859
                    'group'       => 'Lead',
860
                    'optionLabel' => 'Company',
861
                ],
862
            'Email__Lead' => [
863
                    'type'        => 'string',
864
                    'label'       => 'Lead-Email',
865
                    'required'    => false,
866
                    'group'       => 'Lead',
867
                    'optionLabel' => 'Email',
868
                ],
869
        ];
870
        $contactFields = [
871
            'Id__Contact' => [
872
                    'type'        => 'string',
873
                    'label'       => 'Contact-Contact ID',
874
                    'required'    => false,
875
                    'group'       => 'Contact',
876
                    'optionLabel' => 'Contact ID',
877
                ],
878
            'LastName__Contact' => [
879
                    'type'        => 'string',
880
                    'label'       => 'Contact-Last Name',
881
                    'required'    => true,
882
                    'group'       => 'Contact',
883
                    'optionLabel' => 'Last Name',
884
                ],
885
            'FirstName__Contact' => [
886
                    'type'        => 'string',
887
                    'label'       => 'Contact-First Name',
888
                    'required'    => false,
889
                    'group'       => 'Contact',
890
                    'optionLabel' => 'First Name',
891
                ],
892
            'Email__Contact' => [
893
                    'type'        => 'string',
894
                    'label'       => 'Contact-Email',
895
                    'required'    => false,
896
                    'group'       => 'Contact',
897
                    'optionLabel' => 'Email',
898
                ],
899
        ];
900
901
        $this->cache
902
            ->method('get')
903
            ->willReturnMap(
904
                [
905
                    ['leadFields.Lead', null, $leadFields],
906
                    ['leadFields.Contact', null, $contactFields],
907
                ]
908
            );
909
910
        $this->cache->method('getCache')
911
            ->willReturn($this->cache);
912
    }
913
914
    /**
915
     * @param int  $maxUpdate
916
     * @param int  $maxCreate
917
     * @param int  $maxSfLeads
918
     * @param int  $maxSfContacts
919
     * @param null $updateObject
920
     *
921
     * @return SalesforceIntegration|\PHPUnit\Framework\MockObject\MockObject
922
     */
923
    protected function getSalesforceIntegration($maxUpdate = 100, $maxCreate = 200, $maxSfLeads = 25, $maxSfContacts = 25, $updateObject = null)
924
    {
925
        $this->setMocks();
926
927
        $featureSettings = [
928
            'sandbox' => [
929
                ],
930
            'updateOwner' => [
931
                ],
932
            'objects'    => $this->sfObjects,
933
            'namespace'  => null,
934
            'leadFields' => [
935
                    'Company__Lead'      => 'company',
936
                    'FirstName__Lead'    => 'firstname',
937
                    'LastName__Lead'     => 'lastname',
938
                    'Email__Lead'        => 'email',
939
                    'FirstName__Contact' => 'firstname',
940
                    'LastName__Contact'  => 'lastname',
941
                    'Email__Contact'     => 'email',
942
                ],
943
            'update_mautic' => [
944
                    'Company__Lead'      => '0',
945
                    'FirstName__Lead'    => '0',
946
                    'LastName__Lead'     => '0',
947
                    'Email__Lead'        => '0',
948
                    'FirstName__Contact' => '0',
949
                    'LastName__Contact'  => '0',
950
                    'Email__Contact'     => '0',
951
                ],
952
            'companyFields' => [
953
                    'Name' => 'companyname',
954
                ],
955
            'update_mautic_company' => [
956
                    'Name' => '0',
957
                ],
958
        ];
959
960
        $integration = new Integration();
961
        $integration->setIsPublished(true)
962
            ->setName('Salesforce')
963
            ->setPlugin('MauticCrmBundle')
964
            ->setApiKeys(
965
                [
966
                    'access_token' => '123',
967
                    'instance_url' => 'https://sftest.com',
968
                ]
969
            )
970
            ->setFeatureSettings($featureSettings)
971
            ->setSupportedFeatures(
972
                [
973
                    'get_leads',
974
                    'push_lead',
975
                    'push_leads',
976
                ]
977
            );
978
979
        $integrationEntityModelMock = $this->getMockBuilder(IntegrationEntityModel::class)
980
            ->disableOriginalConstructor()
981
            ->getMock();
982
983
        $integrationEntityModelMock->method('getEntityByIdAndSetSyncDate')
984
            ->willReturn(new IntegrationEntity());
985
986
        $sf = $this->getMockBuilder(SalesforceIntegration::class)
987
            ->setConstructorArgs([
988
                $this->dispatcher,
989
                $this->cache,
990
                $this->em,
991
                $this->session,
992
                $this->request,
993
                $this->router,
994
                $this->translator,
995
                $this->logger,
996
                $this->encryptionHelper,
997
                $this->leadModel,
998
                $this->companyModel,
999
                $this->pathsHelper,
1000
                $this->notificationModel,
1001
                $this->fieldModel,
1002
                $integrationEntityModelMock,
1003
                $this->doNotContact,
1004
            ])
1005
            ->setMethods($this->sfMockMethods)
1006
            ->getMock();
1007
1008
        $sf->method('makeRequest')
1009
            ->will(
1010
                $this->returnCallback(
1011
                    function () use ($maxSfContacts, $maxSfLeads, $updateObject) {
1012
                        $args = func_get_args();
1013
                        // Determine what to return by analyzing the URL and query parameters
1014
                        switch (true) {
1015
                            case false !== strpos($args[0], '/query'):
1016
                                if (isset($args[1]['q']) && false !== strpos($args[0], 'from CampaignMember')) {
1017
                                    return [];
1018
                                } elseif (isset($args[1]['q']) && false !== strpos($args[1]['q'], 'from Campaign')) {
1019
                                    return [
1020
                                        'totalSize' => 0,
1021
                                        'records'   => [],
1022
                                    ];
1023
                                } elseif (isset($args[1]['q']) && false !== strpos($args[1]['q'], 'from Account')) {
1024
                                    return [
1025
                                        'totalSize' => 0,
1026
                                        'records'   => [],
1027
                                    ];
1028
                                } elseif (isset($args[1]['q']) && 'SELECT CreatedDate from Organization' === $args[1]['q']) {
1029
                                    return [
1030
                                        'records' => [
1031
                                            ['CreatedDate' => '2012-10-30T17:56:50.000+0000'],
1032
                                        ],
1033
                                    ];
1034
                                } elseif (isset($args[1]['q']) && false !== strpos($args[1]['q'], 'from '.$updateObject.'History')) {
1035
                                    return $this->getSalesforceDNCHistory($updateObject, 'Mautic');
1036
                                } else {
1037
                                    // Extract emails
1038
                                    $found = preg_match('/Email in \(\'(.*?)\'\)/', $args[1]['q'], $match);
1039
                                    if ($found) {
1040
                                        $emails = explode("','", $match[1]);
1041
1042
                                        return $this->getSalesforceObjects($emails, $maxSfContacts, $maxSfLeads);
1043
                                    } else {
1044
                                        return $this->getSalesforceObjects([], $maxSfContacts, $maxSfLeads);
1045
                                    }
1046
                                }
1047
                                // no break
1048
                            case false !== strpos($args[0], '/composite'):
1049
                                return $this->getSalesforceCompositeResponse($args[1]);
1050
                        }
1051
                    }
1052
                )
1053
            );
1054
1055
        /* @var \PHPUnit\Framework\MockObject\MockObject $this->>dispatcher */
1056
        $this->dispatcher->method('dispatch')
1057
            ->will(
1058
                $this->returnCallback(
1059
                    function () use ($sf, $integration) {
1060
                        $args = func_get_args();
1061
1062
                        switch ($args[0]) {
1063
                            default:
1064
                                return new PluginIntegrationKeyEvent($sf, $integration->getApiKeys());
1065
                        }
1066
                    }
1067
                )
1068
            );
1069
1070
        $sf->setIntegrationSettings($integration);
1071
1072
        $repo = $sf->getIntegrationEntityRepository();
1073
        $this->setLeadsToUpdate($repo, $maxUpdate, $maxSfContacts, $maxSfLeads, $updateObject);
1074
        $this->setLeadsToCreate($repo, $maxCreate);
1075
1076
        return $sf;
1077
    }
1078
1079
    /**
1080
     * @param $max
1081
     * @param $maxSfContacts
1082
     * @param $maxSfLeads
1083
     * @param $specificObject
1084
     */
1085
    protected function setLeadsToUpdate(MockObject $mockRepository, $max, $maxSfContacts, $maxSfLeads, $specificObject)
1086
    {
1087
        $mockRepository->method('findLeadsToUpdate')
1088
            ->willReturnCallback(
1089
                function () use ($max, $specificObject) {
1090
                    $args = func_get_args();
1091
                    $object = $args[6];
1092
1093
                    // determine whether to return a count or records
1094
                    $results = [];
1095
                    if (false === $args[3]) {
1096
                        foreach ($object as $object) {
1097
                            if ($specificObject && $specificObject !== $object) {
1098
                                continue;
1099
                            }
1100
1101
                            // Should be 100 contacts and 100 leads
1102
                            $results[$object] = $max;
1103
                        }
1104
1105
                        return $results;
1106
                    }
1107
1108
                    $results = $this->getLeadsToUpdate($object, $args[3], $max, $specificObject);
1109
1110
                    return $results;
1111
                }
1112
            );
1113
    }
1114
1115
    /**
1116
     * @param int $max
1117
     */
1118
    protected function setLeadsToCreate(MockObject $mockRepository, $max = 200)
1119
    {
1120
        $mockRepository->method('findLeadsToCreate')
1121
            ->willReturnCallback(
1122
                function () use ($max) {
1123
                    $args = func_get_args();
1124
1125
                    if (false === $args[2]) {
1126
                        return $max;
1127
                    }
1128
1129
                    $createLeads = $this->getLeadsToCreate($args[2], $max);
1130
1131
                    // determine whether to return a count or records
1132
                    if (false === $args[2]) {
1133
                        return count($createLeads);
1134
                    }
1135
1136
                    return $createLeads;
1137
                }
1138
            );
1139
    }
1140
1141
    /**
1142
     * Simulate looping over Mautic leads to update.
1143
     *
1144
     * @param $object
1145
     * @param $limit
1146
     * @param $max
1147
     * @param $specificObject
1148
     *
1149
     * @return array
1150
     */
1151
    protected function getLeadsToUpdate($object, $limit, $max, $specificObject)
1152
    {
1153
        $entities = [
1154
            $object => [],
1155
        ];
1156
1157
        // Should be 100 each
1158
        if (($this->leadsUpdatedCounter[$object] >= $max) || ($specificObject && $specificObject !== $object)) {
1159
            return $entities;
1160
        }
1161
1162
        if ($limit > $max) {
1163
            $limit = $max;
1164
        }
1165
1166
        $counter = 0;
1167
        while ($counter < $limit) {
1168
            $entities[$object][$this->idCounter] = [
1169
                'integration_entity_id' => 'SF'.$this->idCounter,
1170
                'integration_entity'    => $object,
1171
                'id'                    => $this->idCounter,
1172
                'internal_entity_id'    => $this->idCounter,
1173
                'firstname'             => $object.$this->idCounter,
1174
                'lastname'              => $object.$this->idCounter,
1175
                'company'               => $object.$this->idCounter,
1176
                'email'                 => $object.$this->idCounter.'@sftest.com',
1177
            ];
1178
1179
            ++$counter;
1180
            ++$this->idCounter;
1181
            ++$this->leadsUpdatedCounter[$object];
1182
        }
1183
1184
        $this->mauticContacts = array_merge($entities[$object], $this->mauticContacts);
1185
1186
        return $entities;
1187
    }
1188
1189
    /**
1190
     * Simulate looping over Mautic leads to create.
1191
     *
1192
     * @param $limit
1193
     * @param $max
1194
     *
1195
     * @return array
1196
     */
1197
    protected function getLeadsToCreate($limit, $max = 200)
1198
    {
1199
        $entities = [];
1200
1201
        if ($this->leadsCreatedCounter > $max) {
1202
            return $entities;
1203
        }
1204
1205
        if ($limit > $max) {
1206
            $limit = $max;
1207
        }
1208
1209
        $counter = 0;
1210
        while ($counter < $limit) {
1211
            //Start after the update
1212
            $entities[$this->idCounter] = [
1213
                'id'                 => $this->idCounter,
1214
                'internal_entity_id' => $this->idCounter,
1215
                'firstname'          => 'Lead'.$this->idCounter,
1216
                'lastname'           => 'Lead'.$this->idCounter,
1217
                'company'            => 'Lead'.$this->idCounter,
1218
                'email'              => 'Lead'.$this->idCounter.'@sftest.com',
1219
            ];
1220
1221
            ++$this->idCounter;
1222
            ++$counter;
1223
            ++$this->leadsCreatedCounter;
1224
        }
1225
1226
        $this->mauticContacts = array_merge($entities, $this->mauticContacts);
1227
1228
        return $entities;
1229
    }
1230
1231
    /**
1232
     * Mock SF response.
1233
     *
1234
     * @return array
1235
     */
1236
    protected function getSalesforceObjects($emails, $maxContacts, $maxLeads)
1237
    {
1238
        // Let's find around $max records
1239
        $records      = [];
1240
        $contactCount = 0;
1241
        $leadCount    = 0;
1242
1243
        if (!empty($emails)) {
1244
            foreach ($emails as $email) {
1245
                // Extact ID
1246
                preg_match('/(Lead|Contact)([0-9]*)@sftest\.com/', $email, $match);
1247
                $object = $match[1];
1248
1249
                if ('Lead' === $object) {
1250
                    if ($leadCount >= $maxLeads) {
1251
                        continue;
1252
                    }
1253
                    ++$leadCount;
1254
                } else {
1255
                    if ($contactCount >= $maxContacts) {
1256
                        continue;
1257
                    }
1258
                    ++$contactCount;
1259
                }
1260
1261
                $id        = $match[2];
1262
                $records[] = [
1263
                    'attributes' => [
1264
                        'type' => $object,
1265
                        'url'  => "/services/data/v34.0/sobjects/$object/SF$id",
1266
                    ],
1267
                    'Id'        => 'SF'.$id,
1268
                    'FirstName' => $object.$id,
1269
                    'LastName'  => $object.$id,
1270
                    'Email'     => $object.$id.'@sftest.com',
1271
                ];
1272
1273
                $this->addSpecialCases($id, $records);
1274
            }
1275
        }
1276
1277
        $this->returnedSfEntities = array_merge($this->returnedSfEntities, $records);
1278
1279
        return [
1280
            'totalSize' => count($records),
1281
            'done'      => true,
1282
            'records'   => $records,
1283
        ];
1284
    }
1285
1286
    /**
1287
     * Mock SF response.
1288
     *
1289
     * @return array
1290
     */
1291
    protected function getSalesforceDNCHistory($object, $priority)
1292
    {
1293
        $records      = [];
0 ignored issues
show
The assignment to $records is dead and can be removed.
Loading history...
1294
        $datePriority = [
1295
            'SF'     => '2017-10-16T00:43:43.000+0000',
1296
            'Mautic' => '2017-10-16T18:43:43.000+0000',
1297
            ];
1298
1299
        $records = [
1300
            'totalSize' => 3,
1301
            'done'      => 1,
1302
            'records'   => [
1303
                [
1304
                    'attributes' => [
1305
                        'type' => 'ContactHistory',
1306
                        'url'  => '/services/data/v34.0/sobjects/'.$object.'History/0170SFH1',
1307
                        ],
1308
                    'Field'       => 'HasOptedOutOfEmail',
1309
                    $object.'Id'  => 'SF1',
1310
                    'CreatedDate' => $datePriority[$priority],
1311
                    'IsDeleted'   => false,
1312
                    'NewValue'    => true,
1313
                ],
1314
                [
1315
                    'attributes' => [
1316
                        'type' => 'ContactHistory',
1317
                        'url'  => '/services/data/v34.0/sobjects/'.$object.'History/0170SFH3',
1318
                    ],
1319
                    'Field'       => 'HasOptedOutOfEmail',
1320
                    $object.'Id'  => 'SF2',
1321
                    'CreatedDate' => $datePriority[$priority],
1322
                    'IsDeleted'   => false,
1323
                    'NewValue'    => true,
1324
                ],
1325
            ],
1326
        ];
1327
1328
        return $records;
1329
    }
1330
1331
    /**
1332
     * @param $id
1333
     * @param $records
1334
     */
1335
    protected function addSpecialCases($id, &$records)
1336
    {
1337
        switch ($this->specialSfCase) {
1338
            case self::SC_MULTIPLE_SF_LEADS:
1339
                $records[] = [
1340
                    'attributes' => [
1341
                            'type' => 'Lead',
1342
                            'url'  => '/services/data/v34.0/sobjects/Lead/SF'.$id.'b',
1343
                        ],
1344
                    'Id'                 => 'SF'.$id.'b',
1345
                    'FirstName'          => 'Lead'.$id,
1346
                    'LastName'           => 'Lead'.$id,
1347
                    'Email'              => 'Lead'.$id.'@sftest.com',
1348
                    'Company'            => 'Lead'.$id,
1349
                    'ConvertedContactId' => null,
1350
                ];
1351
                break;
1352
1353
            case self::SC_MULTIPLE_SF_CONTACTS:
1354
                $records[] = [
1355
                    'attributes' => [
1356
                            'type' => 'Contact',
1357
                            'url'  => '/services/data/v34.0/sobjects/Contact/SF'.$id.'b',
1358
                        ],
1359
                    'Id'                 => 'SF'.$id.'b',
1360
                    'FirstName'          => 'Contact'.$id,
1361
                    'LastName'           => 'Contact'.$id,
1362
                    'Email'              => 'Contact'.$id.'@sftest.com',
1363
                    'Company'            => 'Contact'.$id,
1364
                    'ConvertedContactId' => null,
1365
                ];
1366
                break;
1367
        }
1368
    }
1369
1370
    /**
1371
     * Mock SF response.
1372
     *
1373
     * @param $data
1374
     *
1375
     * @return array
1376
     */
1377
    protected function getSalesforceCompositeResponse($data)
1378
    {
1379
        $response = [];
1380
        foreach ($data['compositeRequest'] as $subrequest) {
1381
            if ('PATCH' === $subrequest['method']) {
1382
                $response[] = [
1383
                    'body'           => null,
1384
                    'httpHeaders'    => [],
1385
                    'httpStatusCode' => 204,
1386
                    'referenceId'    => $subrequest['referenceId'],
1387
                ];
1388
            } else {
1389
                $contactId = '';
1390
                $parts     = explode('-', $subrequest['referenceId']);
1391
1392
                if (3 === count($parts)) {
1393
                    list($contactId, $sfObject, $id) = $parts;
1394
                } elseif (2 === count($parts)) {
1395
                    list($contactId, $sfObject) = $parts;
1396
                } elseif (4 === count($parts)) {
1397
                    list($contactId, $sfObject, $empty, $campaignId) = $parts;
1398
                }
1399
                $response[] = [
1400
                    'body' => [
1401
                        'id'      => 'SF'.$contactId,
1402
                        'success' => true,
1403
                        'errors'  => [],
1404
                    ],
1405
                    'httpHeaders' => [
1406
                        'Location' => '/services/data/v38.0/sobjects/'.$sfObject.'/SF'.$contactId,
1407
                    ],
1408
                    'httpStatusCode' => 201,
1409
                    'referenceId'    => $subrequest['referenceId'],
1410
                ];
1411
            }
1412
        }
1413
1414
        return ['compositeResponse' => $response];
1415
    }
1416
1417
    /**
1418
     * @return array
1419
     */
1420
    protected function getPersistedIntegrationEntities()
1421
    {
1422
        $entities                           = $this->persistedIntegrationEntities;
1423
        $this->persistedIntegrationEntities = [];
1424
1425
        return $entities;
1426
    }
1427
1428
    protected function getReturnedSfEntities()
1429
    {
1430
        $entities                 = $this->returnedSfEntities;
1431
        $this->returnedSfEntities = [];
1432
1433
        return $entities;
1434
    }
1435
1436
    protected function getMauticContacts()
1437
    {
1438
        $contacts             = $this->mauticContacts;
1439
        $this->mauticContacts = [
1440
            'Contact' => [],
1441
            'Lead'    => [],
1442
        ];
1443
1444
        return $contacts;
1445
    }
1446
1447
    /**
1448
     * This function determines the parent of the class instance provided, and returns all properties of its parent.
1449
     * Inspired from https://github.com/sebastianbergmann/phpunit/issues/3888#issuecomment-559513371.
1450
     *
1451
     * Result structure:
1452
     *  Array =>[
1453
     *     'parentPropertyName1' => 'value1'
1454
     *     'parentPropertyName2' => 'value2'
1455
     *     ...
1456
     *  ]
1457
     *
1458
     * @param $instance
1459
     *
1460
     * @throws \ReflectionException
1461
     */
1462
    private static function getParentPrivateProperties($instance): array
1463
    {
1464
        $reflectionClass       = new ReflectionClass(get_class($instance));
1465
        $parentReflectionClass = $reflectionClass->getParentClass();
1466
1467
        $parentProperties = [];
1468
1469
        foreach ($parentReflectionClass->getProperties() as $p) {
1470
            $p->setAccessible(true);
1471
            $parentProperties[$p->getName()] = $p->getValue($instance);
1472
        }
1473
1474
        return $parentProperties;
1475
    }
1476
}
1477