0011562: adding two new tasks fails when saving lead
[tine20] / tests / tine20 / Crm / JsonTest.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Crm
6  * @license     http://www.gnu.org/licenses/agpl.html
7  * @copyright   Copyright (c) 2008-2015 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * 
10  */
11
12 /**
13  * Test class for Crm_Json
14  */
15 class Crm_JsonTest extends Crm_AbstractTest
16 {
17     /**
18      * @var array test objects
19      */
20     protected $_objects = array();
21     
22     /**
23      * Backend
24      *
25      * @var Crm_Frontend_Json
26      */
27     protected $_instance;
28     
29     /**
30      * fs controller
31      *
32      * @var Tinebase_FileSystem
33      */
34     protected $_fsController;
35
36
37     /**
38      * customfield name
39      *
40      * @var string
41      */
42     protected $_cfcName = null;
43
44    /**
45      * Sets up the fixture.
46      * This method is called before a test is executed.
47      *
48      * @access protected
49      */
50     protected function setUp()
51     {
52         parent::setUp();
53         
54         $this->_instance = new Crm_Frontend_Json();
55         $this->_fsController = Tinebase_FileSystem::getInstance();
56         Crm_Controller_Lead::getInstance()->duplicateCheckFields(array());
57     }
58
59     /**
60      * Tears down the fixture
61      * This method is called after a test is executed.
62      *
63      * @access protected
64      */
65     protected function tearDown()
66     {
67         if (isset($this->_objects['paths'])) {
68             foreach ($this->_objects['paths'] as $path) {
69                 try {
70                     $this->_fsController->rmdir($path, TRUE);
71                 } catch (Tinebase_Exception_NotFound $tenf) {
72                     // already deleted
73                 }
74             }
75         }
76         
77         parent::tearDown();
78         Crm_Controller_Lead::getInstance()->duplicateCheckFields(array('lead_name'));
79     }
80      
81     /**
82      * test get crm registry
83      * 
84      * @return void
85      */
86     public function testGetRegistryData()
87     {
88         $registry = $this->_instance->getRegistryData();
89         
90         $types = array('leadtypes', 'leadstates', 'leadsources');
91         
92         // check data
93         foreach ($types as $type) {
94             $this->assertGreaterThan(0, $registry[$type]['totalcount']);
95             $this->assertGreaterThan(0, count($registry[$type]['results']));
96         }
97         
98         // check defaults
99         $this->assertEquals(array(
100             'leadstate_id'  => 1,
101             'leadtype_id'   => 1,
102             'leadsource_id' => 1,
103         ), array(
104             'leadstate_id' => $registry['defaults']['leadstate_id'],
105             'leadtype_id' => $registry['defaults']['leadtype_id'],
106             'leadsource_id' => $registry['defaults']['leadsource_id'],
107         ));
108         $this->assertEquals(
109             Tinebase_Container::getInstance()->getDefaultContainer('Crm')->getId(),
110             $registry['defaults']['container_id']['id']
111         );
112     }
113     
114     /**
115      * test get settings/config
116      * 
117      * @return void
118      */
119     public function testGetSettings()
120     {
121         $result = $this->_instance->getSettings();
122         
123         $this->assertArrayHasKey('leadstates',  $result);
124         $this->assertArrayHasKey('leadtypes',   $result);
125         $this->assertArrayHasKey('leadsources', $result);
126         $this->assertArrayHasKey('defaults',    $result);
127         $this->assertEquals(6, count($result[Crm_Model_Config::LEADSTATES]));
128         $this->assertEquals(3, count($result[Crm_Model_Config::LEADTYPES]));
129         $this->assertEquals(4, count($result[Crm_Model_Config::LEADSOURCES]));
130     }
131     
132     /**
133      * test get settings/config
134      * 
135      * @return void
136      */
137     public function testSaveSettings()
138     {
139         $oldSettings = $this->_instance->getSettings();
140         
141         // change some settings
142         $newSettings = $oldSettings;
143         $newSettings['defaults']['leadstate_id'] = 2;
144         $newSettings['leadsources'][] = array(
145             'id' => 5,
146             'leadsource' => 'Another Leadsource'
147         );
148         $anotherResult = $this->_instance->saveSettings($newSettings);
149         $this->assertEquals($newSettings, $anotherResult, 'new settings have not been saved');
150         
151         // reset original settings
152         $result = $this->_instance->saveSettings($oldSettings);
153         $this->assertEquals($result, $oldSettings, 'old settings have not been reset');
154         
155         // test Crm_Model_Config::getOptionById
156         $settings = Crm_Controller::getInstance()->getConfigSettings();
157         $this->assertEquals(array(), $settings->getOptionById(5, 'leadsources'), 'Crm_Model_Config::getOptionById failed');
158     }
159     
160     /**
161      * try to add/search/delete a lead with linked contact, task and product
162      * 
163      * @see 0007214: if lead with linked task is saved, alarm is discarded
164      */
165     public function testAddGetSearchDeleteLead()
166     {
167         $savedLead = $this->_saveLead();
168         $getLead = $this->_instance->getLead($savedLead['id']);
169         $searchLeads = $this->_instance->searchLeads($this->_getLeadFilter(), '');
170         
171         // test manual resolving of organizer in related_record and set it back for following tests
172         for ($i = 0; $i < count($getLead['relations']); $i++) {
173             if (isset($getLead['relations'][$i]['related_record']['organizer'])) {
174                 $this->assertTrue(is_array($getLead['relations'][$i]['related_record']['organizer']));
175                 $getLead['relations'][$i]['related_record']['organizer'] = $getLead['relations'][$i]['related_record']['organizer']['accountId'];
176             }
177         }
178         
179         // assertions
180         $this->assertEquals($getLead, $savedLead);
181         $this->assertEquals($getLead['notes'][0]['note'], 'phpunit test note');
182         $this->assertTrue($searchLeads['totalcount'] > 0);
183         $this->assertTrue(isset($searchLeads['totalleadstates']) && count($searchLeads['totalleadstates']) > 0);
184         $this->assertEquals($getLead['description'], $searchLeads['results'][0]['description']);
185         $this->assertEquals(200, $searchLeads['results'][0]['turnover'], 'turnover has not been calculated using product prices');
186         $this->assertEquals($searchLeads['results'][0]['turnover']*$getLead['probability']/100, $searchLeads['results'][0]['probableTurnover']);
187         // now we need 2 relations here (frontend search shall return relations with related_model Addressbook_Model_Contact or Sales_Model_Product
188         $this->assertEquals(2, count($searchLeads['results'][0]['relations']), 'did not get all relations');
189
190         $relatedTask = null;
191         foreach($getLead['relations'] as $rel) {
192             if ($rel['type'] == 'TASK') {
193                 $relatedTask = $rel['related_record'];
194             }
195         }
196
197         $this->assertTrue($relatedTask !== null);
198         $this->assertEquals($this->_getTask()->summary, $relatedTask['summary'], 'task summary does not match');
199         $defaultTaskContainerId = Tinebase_Core::getPreference('Tasks')->getValue(Tasks_Preference::DEFAULTTASKLIST);
200         $this->assertEquals($defaultTaskContainerId, $relatedTask['container_id']);
201         $this->assertTrue(isset($relatedTask['alarms']) && count($relatedTask['alarms']) === 1, 'alarm missing in related task: ' . print_r($relatedTask, TRUE));
202         
203         $relatedTaskId = $relatedTask['id'];
204         $relatedTask = NULL;
205         
206         // get related records and check relations
207         foreach ($searchLeads['results'][0]['relations'] as $relation) {
208             switch ($relation['type']) {
209                 case 'PRODUCT':
210                     //print_r($relation);
211                     $this->assertEquals(200, $relation['remark']['price'], 'product price (remark) does not match');
212                     $relatedProduct = $relation['related_record'];
213                     break;
214                 case 'TASK':
215                     $relatedTask = $relation['related_record'];
216                     break;
217                 case 'PARTNER':
218                     $relatedContact = $relation['related_record'];
219                     break;
220             }
221         }
222         $this->assertTrue(isset($relatedContact), 'contact not found');
223         $this->assertEquals($this->_getContact()->n_fn, $relatedContact['n_fn'], 'contact name does not match');
224         
225         $this->assertFalse(is_array($relatedTask), 'task must not be found');
226         
227         $this->assertTrue(isset($relatedProduct), 'product not found');
228         $this->assertEquals($this->_getProduct()->name, $relatedProduct['name'], 'product name does not match');
229         
230         // delete all
231         $this->_instance->deleteLeads($savedLead['id']);
232         Addressbook_Controller_Contact::getInstance()->delete($relatedContact['id']);
233         Sales_Controller_Product::getInstance()->delete($relatedProduct['id']);
234         
235         // check if delete worked
236         $result = $this->_instance->searchLeads($this->_getLeadFilter(), '');
237         $this->assertEquals(0, $result['totalcount']);
238         
239         // check if linked task got removed as well
240         $this->setExpectedException('Tinebase_Exception_NotFound');
241         Tasks_Controller_Task::getInstance()->get($relatedTaskId);
242     }
243     
244     /**
245      * save lead with relations
246      * 
247      * @return array
248      */
249     protected function _saveLead()
250     {
251         $contact    = $this->_getContact();
252         $task       = $this->_getTask();
253         $lead       = $this->_getLead();
254         $product    = $this->_getProduct();
255         $price      = 200;
256         
257         $leadData = $lead->toArray();
258         $leadData['relations'] = array(
259             array('type'  => 'TASK',    'related_record' => $task->toArray()),
260             array('type'  => 'PARTNER', 'related_record' => $contact->toArray()),
261             array('type'  => 'PRODUCT', 'related_record' => $product->toArray(), 'remark' => array('price' => $price)),
262         );
263         // add note
264         $note = array(
265             'note_type_id'      => 1,
266             'note'              => 'phpunit test note',
267         );
268         $leadData['notes'] = array($note);
269         
270         $savedLead = $this->_instance->saveLead($leadData);
271         return $savedLead;
272     }
273     
274     /**
275      * test tag filter (adds a contact with the same id + tag)
276      * 
277      * see bug #4834 (http://forge.tine20.org/mantisbt/view.php?id=4834)
278      */
279     public function testTagFilter()
280     {
281         $lead       = $this->_getLead();
282         $savedLead = $this->_instance->saveLead($lead->toArray());
283         
284         $sharedTagName = Tinebase_Record_Abstract::generateUID();
285         $tag = new Tinebase_Model_Tag(array(
286             'type'  => Tinebase_Model_Tag::TYPE_SHARED,
287             'name'  => $sharedTagName,
288             'description' => 'testTagFilter',
289             'color' => '#009B31',
290         ));
291         $contact    = $this->_getContact();
292         $contact->setId($savedLead['id']);
293         
294         $contact->tags = array($tag);
295         $savedContact = Addressbook_Controller_Contact::getInstance()->create($contact, FALSE);
296         $tag = $savedContact->tags->getFirstRecord();
297         
298         $filter = array(
299             array('field' => 'tag',           'operator' => 'equals',       'value' => $tag->getId()),
300         );
301         
302         $result = $this->_instance->searchLeads($filter, array());
303         $this->assertEquals(0, $result['totalcount'], 'Should not find the lead!');
304     }    
305     
306     /**
307      * testSearchByBrokenFilter
308      * 
309      * @see 0005990: cardinality violation when searching for leads / http://forge.tine20.org/mantisbt/view.php?id=5990
310      */
311     public function testSearchByBrokenFilter()
312     {
313         $filter = Zend_Json::decode('[{"field":"query","operator":"contains","value":"test"},{"field":"container_id","operator":"equals","value":{"path":"/"}},{"field":"contact","operator":"AND","value":[{"field":":id","operator":"equals","value":{"n_fn":"","n_fileas":"","org_name":"","container_id":"2576"}}]}]');
314         $result = $this->_instance->searchLeads($filter, array());
315         $this->assertEquals(0, $result['totalcount']);
316     }
317     
318     /**
319      * add relation, remove relation and add relation again
320      * 
321      * see bug #4840 (http://forge.tine20.org/mantisbt/view.php?id=4840)
322      */
323     public function testAddRelationAgain()
324     {
325         $contact    = $this->_getContact();
326         $savedContact = Addressbook_Controller_Contact::getInstance()->create($contact, FALSE);
327         $lead       = $this->_getLead();
328         
329         $leadData = $lead->toArray();
330         $leadData['relations'] = array(
331             array('type'  => 'PARTNER', 'related_record' => $savedContact->toArray()),
332         );
333         $savedLead = $this->_instance->saveLead($leadData);
334         
335         $savedLead['relations'] = array();
336         $savedLead = $this->_instance->saveLead($savedLead);
337         $this->assertEquals(0, count($savedLead['relations']));
338         
339         $savedLead['relations'] = array(
340             array('type'  => 'PARTNER', 'related_record' => $savedContact->toArray()),
341         );
342         $savedLead = $this->_instance->saveLead($savedLead);
343         
344         $this->assertEquals(1, count($savedLead['relations']), 'Relation has not been added');
345         $this->assertEquals($contact->n_fn, $savedLead['relations'][0]['related_record']['n_fn'], 'Contact name does not match');
346     }
347     
348     /**
349      * testRelationWithoutType
350      * 
351      * @see 0006206: relation type field can be empty
352      */
353     public function testRelationWithoutType()
354     {
355         $contact      = $this->_getContact();
356         $savedContact = Addressbook_Controller_Contact::getInstance()->create($contact, FALSE);
357         $lead         = $this->_getLead();
358         
359         $leadData = $lead->toArray();
360         $leadData['relations'] = array(
361             array('type'  => '', 'related_record' => $savedContact->toArray()),
362         );
363         $savedLead = $this->_instance->saveLead($leadData);
364         
365         $this->assertEquals(1, count($savedLead['relations']), 'Relation has not been added');
366         $this->assertEquals('CUSTOMER', $savedLead['relations'][0]['type'], 'default type should be CUSTOMER');
367     }
368     
369     /**
370      * testConcurrentRelationSetting
371      * 
372      * @see 0007108: inspect and solve concurrency conflicts when setting lead relations
373      * @see 0000554: modlog: records can't be updated in less than 1 second intervals
374      */
375     public function testConcurrentRelationSetting()
376     {
377         $leadData = $this->_instance->saveLead($this->_getLead()->toArray());
378         $task = $this->_getTask();
379         
380         $taskJson = new Tasks_Frontend_Json();
381         $taskData = $task->toArray();
382         $taskData['relations'] = array(
383             array(
384                 'type'  => 'TASK',
385                 'own_model' => 'Tasks_Model_Task',
386                 'own_backend' => 'Sql',
387                 'own_degree' => 'sibling',
388                 'related_model' => 'Crm_Model_Lead',
389                 'related_backend' => 'Sql',
390                 'related_id' => $leadData['id'],
391                 'related_record' => $leadData
392             ),
393         );
394         
395         $taskData = $taskJson->saveTask($taskData);
396         $taskData['description'] = 1;
397         $taskJson->saveTask($taskData);
398         
399         $savedLead = $this->_instance->getLead($leadData['id']);
400         $savedLead['relations'][0]['related_record']['description'] = '2';
401         $savedLead['relations'][0]['related_record']['due'] = '2012-10-18 12:54:33';
402         
403         // client may send wrong seq -> this should cause a concurrency conflict
404         $savedLead['relations'][0]['related_record']['seq'] = 0;
405         try {
406             $this->_instance->saveLead($savedLead);
407             $this->fail('expected concurrency exception');
408         } catch (Tinebase_Timemachine_Exception_ConcurrencyConflict $ttecc) {
409             $this->assertEquals('concurrency conflict!', $ttecc->getMessage());
410         }
411     }
412     
413     /**
414      * @see #8840: relations config - constraints from the other side
415      *      - validate in backend
416      *      
417      *      https://forge.tine20.org/mantisbt/view.php?id=8840
418      */
419     public function testConstraintsOtherSide()
420     {
421         $leadData1 = $this->_instance->saveLead($this->_getLead(FALSE, FALSE)->toArray());
422         $task = $this->_getTask();
423         
424         $taskJson = new Tasks_Frontend_Json();
425         $taskData = $task->toArray();
426         $taskData['relations'] = array(
427             array(
428                 'type'  => 'TASK',
429                 'own_model' => 'Tasks_Model_Task',
430                 'own_backend' => 'Sql',
431                 'own_degree' => 'sibling',
432                 'related_model' => 'Crm_Model_Lead',
433                 'related_backend' => 'Sql',
434                 'related_id' => $leadData1['id'],
435                 'related_record' => $leadData1
436             ),
437         );
438         
439         $taskData = $taskJson->saveTask($taskData);
440         
441         $leadData2 = $this->_instance->saveLead($this->_getLead(FALSE, FALSE)->toArray());
442         $taskData['relations'][] = array(
443             'type'  => 'TASK',
444             'own_model' => 'Tasks_Model_Task',
445             'own_backend' => 'Sql',
446             'own_degree' => 'sibling',
447             'related_model' => 'Crm_Model_Lead',
448             'related_backend' => 'Sql',
449             'related_id' => $leadData2['id'],
450             'related_record' => $leadData2
451         );
452         
453         $this->setExpectedException('Tinebase_Exception_InvalidRelationConstraints');
454         $taskJson->saveTask($taskData);
455     }
456     
457     /**
458      * testOtherRecordConstraintsConfig
459      */
460     public function testOtherRecordConstraintsConfig()
461     {
462         $leadData1 = $this->_instance->saveLead($this->_getLead(FALSE, FALSE)->toArray());
463         $task = $this->_getTask();
464         
465         $taskJson = new Tasks_Frontend_Json();
466         $leadJson = new Crm_Frontend_Json();
467         
468         $taskData = $task->toArray();
469         $taskData['relations'] = array(
470             array(
471                 'type'  => 'TASK',
472                 'own_model' => 'Tasks_Model_Task',
473                 'own_backend' => 'Sql',
474                 'own_degree' => 'sibling',
475                 'related_model' => 'Crm_Model_Lead',
476                 'related_backend' => 'Sql',
477                 'related_id' => $leadData1['id'],
478                 'related_record' => $leadData1
479             ),
480         );
481         
482         $taskData = $taskJson->saveTask($taskData);
483         
484         $leadData2 = $this->_instance->saveLead($this->_getLead(FALSE, FALSE)->toArray());
485         
486         $leadData2['relations'] = array(
487             array(
488                 'type'  => 'TASK',
489                 'own_model' => 'Crm_Model_Lead',
490                 'own_backend' => 'Sql',
491                 'own_degree' => 'sibling',
492                 'related_model' => 'Tasks_Model_Task',
493                 'related_backend' => 'Sql',
494                 'related_id' => $taskData['id'],
495                 'related_record' => $taskData
496             )
497         );
498         
499         $this->setExpectedException('Tinebase_Exception_InvalidRelationConstraints');
500         
501         $leadJson->saveLead($leadData2);
502     }
503     
504     /**
505      * try to add multiple related tasks with one save
506      *
507      */
508     public function testLeadWithMultipleTasks()
509     {
510         $lead = $this->_getLead();
511         $task1 = $this->_getTask();
512         $task2 = $this->_getTask();
513         
514         
515         $leadData = $lead->toArray();
516         $leadData['relations'] = array(
517                 array('type'  => 'TASK', 'related_record' => $task1->toArray()),
518                 array('type'  => 'TASK', 'related_record' => $task2->toArray())
519         );
520         
521         $savedLead = $this->_getUit()->saveLead($leadData);
522         $this->assertEquals(2, count($savedLead['relations']), 'Relations missing');
523     }
524     
525     /**
526      * get contact
527      * 
528      * @return Addressbook_Model_Contact
529      */
530     protected function _getContact()
531     {
532         return new Addressbook_Model_Contact(array(
533             'adr_one_countryname'   => 'DE',
534             'adr_one_locality'      => 'Hamburg',
535             'adr_one_postalcode'    => '24xxx',
536             'adr_one_region'        => 'Hamburg',
537             'adr_one_street'        => 'Pickhuben 4',
538             'adr_one_street2'       => 'no second street',
539             'adr_two_countryname'   => 'DE',
540             'adr_two_locality'      => 'Hamburg',
541             'adr_two_postalcode'    => '24xxx',
542             'adr_two_region'        => 'Hamburg',
543             'adr_two_street'        => 'Pickhuben 4',
544             'adr_two_street2'       => 'no second street2',
545             'assistent'             => 'Cornelius Weiß',
546             'bday'                  => '1975-01-02 03:04:05', // new Tinebase_DateTime???
547             'email'                 => 'unittests@tine20.org',
548             'email_home'            => 'unittests@tine20.org',
549             'note'                  => 'Bla Bla Bla',
550             'role'                  => 'Role',
551             'title'                 => 'Title',
552             'url'                   => 'http://www.tine20.org',
553             'url_home'              => 'http://www.tine20.com',
554             'n_family'              => 'Kneschke',
555             'n_fileas'              => 'Kneschke, Lars',
556             'n_given'               => 'Lars',
557             'n_middle'              => 'no middle name',
558             'n_prefix'              => 'no prefix',
559             'n_suffix'              => 'no suffix',
560             'org_name'              => 'Metaways Infosystems GmbH',
561             'org_unit'              => 'Tine 2.0',
562             'tel_assistent'         => '+49TELASSISTENT',
563             'tel_car'               => '+49TELCAR',
564             'tel_cell'              => '+49TELCELL',
565             'tel_cell_private'      => '+49TELCELLPRIVATE',
566             'tel_fax'               => '+49TELFAX',
567             'tel_fax_home'          => '+49TELFAXHOME',
568             'tel_home'              => '+49TELHOME',
569             'tel_pager'             => '+49TELPAGER',
570             'tel_work'              => '+49TELWORK',
571         ));
572     }
573
574     /**
575      * get task
576      * 
577      * @return Tasks_Model_Task
578      */
579     protected function _getTask()
580     {
581         return new Tasks_Model_Task(array(
582             'created_by'           => Zend_Registry::get('currentAccount')->getId(),
583             'creation_time'        => Tinebase_DateTime::now(),
584             'percent'              => 70,
585             'due'                  => Tinebase_DateTime::now()->addMonth(1),
586             'summary'              => 'phpunit: crm test task',
587             'alarms'               => new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(array(
588                 'minutes_before'    => 0
589             )), TRUE),
590         ));
591     }
592     
593     /**
594      * get lead
595      * 
596      * @param boolean $addCf
597      * @param boolean $addTags
598      * @return Crm_Model_Lead
599      */
600     protected function _getLead($addCf = TRUE, $addTags = TRUE)
601     {
602         if ($addCf) {
603             $cfc = Tinebase_CustomFieldTest::getCustomField(array(
604                 'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Crm')->getId(),
605                 'model'          => 'Crm_Model_Lead',
606                 'name'           => Tinebase_Record_Abstract::generateUID(),
607             ));
608             $this->_cfcName = $cfc->name;
609             
610             $cfs = array(
611                 $this->_cfcName => '1234'
612             );
613             
614             Tinebase_CustomField::getInstance()->addCustomField($cfc);
615         } else {
616             $cfs = array();
617         }
618         
619         if ($addTags) {
620             $tags = array(
621                 array('name' => 'lead tag', 'type' => Tinebase_Model_Tag::TYPE_SHARED)
622             );
623         } else {
624             $tags = array();
625         }
626         
627         return new Crm_Model_Lead(array(
628             'lead_name'     => 'PHPUnit',
629             'leadstate_id'  => 1,
630             'leadtype_id'   => 1,
631             'leadsource_id' => 1,
632             'container_id'  => Tinebase_Container::getInstance()->getDefaultContainer('Crm')->getId(),
633             'start'         => Tinebase_DateTime::now(),
634             'description'   => 'Description',
635             'end'           => NULL,
636             'turnover'      => 0,
637             'probability'   => 70,
638             'end_scheduled' => NULL,
639             'tags'          => $tags,
640             'customfields'  => $cfs
641         ));
642     }
643     
644     /**
645      * get product
646      * 
647      * @return Sales_Model_Product
648      */
649     protected function _getProduct()
650     {
651         return new Sales_Model_Product(array(
652             'name'  => 'PHPUnit test product',
653             'price' => 10000,
654         ));
655     }
656     
657     /**
658      * get lead filter
659      * 
660      * @return array
661      */
662     protected function _getLeadFilter()
663     {
664         return array(
665             array('field' => 'query',           'operator' => 'contains',       'value' => 'PHPUnit'),
666         );
667     }
668
669     /**
670      * testRelatedModlog
671      * 
672      * @see 0000996: add changes in relations/linked objects to modlog/history
673      */
674     public function testRelatedModlog()
675     {
676         // create lead with tag, customfield and related contacts
677         $savedLead = $this->_saveLead();
678         
679         // change relations, customfields + tags
680         $savedLead['tags'][] = array('name' => 'another tag', 'type' => Tinebase_Model_Tag::TYPE_PERSONAL);
681         foreach ($savedLead['relations'] as $key => $value) {
682             if ($value['type'] == 'PARTNER') {
683                 $savedLead['relations'][$key]['type'] = 'CUSTOMER';
684             }
685             if ($value['type'] == 'TASK') {
686                 unset($savedLead['relations'][$key]);
687             }
688         }
689         $savedLead['customfields'][$this->_cfcName] = '5678';
690         $updatedLead = $this->_instance->saveLead($savedLead);
691         
692         // check modlog + history
693         $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getModifications('Crm', $updatedLead['id']);
694         
695         //print_r($updatedLead);
696         $this->assertEquals(3, count($modifications), 'expected 3 modifications: ' . print_r($modifications->toArray(), TRUE));
697         foreach ($modifications as $modification) {
698             switch ($modification->modified_attribute) {
699                 case 'customfields':
700                     $this->assertEquals('{"' . $this->_cfcName . '":"5678"}', $modification->new_value);
701                     break;
702                 case 'relations':
703                     $diff = new Tinebase_Record_RecordSetDiff(Zend_Json::decode($modification->new_value));
704                     $this->assertEquals(0, count($diff->added));
705                     $this->assertEquals(1, count($diff->removed));
706                     $this->assertEquals(1, count($diff->modified), 'relations modified mismatch: ' . print_r($diff->toArray(), TRUE));
707                     $this->assertTrue(isset($diff->modified[0]['diff']['type']));
708                     $this->assertEquals('CUSTOMER', $diff->modified[0]['diff']['type'], 'type diff is not correct: ' . print_r($diff->toArray(), TRUE));
709                     break;
710                 case 'tags':
711                     $diff = new Tinebase_Record_RecordSetDiff(Zend_Json::decode($modification->new_value));
712                     $this->assertEquals(1, count($diff->added));
713                     $this->assertEquals(0, count($diff->removed));
714                     $this->assertEquals(0, count($diff->modified), 'tags modified mismatch: ' . print_r($diff->toArray(), TRUE));
715                     break;
716                 default:
717                     $this->fail('Invalid modification: ' . print_r($modification->toArray(), TRUE));
718             }
719         }
720     }
721     
722     /**
723      * testCreateLeadWithAttachment
724      * 
725      * @see 0005024: allow to attach external files to records
726      */
727     public function testCreateLeadWithAttachment()
728     {
729         $tempFileBackend = new Tinebase_TempFile();
730         $tempFile = $tempFileBackend->createTempFile(dirname(dirname(__FILE__)) . '/Filemanager/files/test.txt');
731         
732         $lead = $this->_getLead()->toArray();
733         $lead['attachments'] = array(array('tempFile' => $tempFile->toArray()));
734         
735         $savedLead = $this->_instance->saveLead($lead);
736         // add path to files to remove
737         $this->_objects['paths'][] = Tinebase_FileSystem_RecordAttachments::getInstance()->getRecordAttachmentPath(new Crm_Model_Lead($savedLead, TRUE)) . '/' . $tempFile->name;
738         
739         $this->assertTrue(isset($savedLead['attachments']), 'no attachments found');
740         $this->assertEquals(1, count($savedLead['attachments']));
741         $attachment = $savedLead['attachments'][0];
742         $this->assertEquals('text/plain', $attachment['contenttype'], print_r($attachment, TRUE));
743         $this->assertEquals(17, $attachment['size']);
744         $this->assertTrue(is_array($attachment['created_by']), 'user not resolved: ' . print_r($attachment['created_by'], TRUE));
745         $this->assertEquals(Tinebase_Core::getUser()->accountFullName, $attachment['created_by']['accountFullName'], 'user not resolved: ' . print_r($attachment['created_by'], TRUE));
746         
747         return $savedLead;
748     }
749     
750     /**
751      * testUpdateLeadWithAttachment
752      * 
753      * @see 0005024: allow to attach external files to records
754      */
755     public function testUpdateLeadWithAttachment()
756     {
757         $lead = $this->testCreateLeadWithAttachment();
758         $savedLead = $this->_instance->saveLead($lead);
759         $this->assertTrue(isset($savedLead['attachments']), 'no attachments found');
760         $this->assertEquals(1, count($savedLead['attachments']));
761     }
762     
763     /**
764      * testRemoveAttachmentFromLead
765      * 
766      * @see 0005024: allow to attach external files to records
767      */
768     public function testRemoveAttachmentFromLead()
769     {
770         $lead = $this->testCreateLeadWithAttachment();
771         $lead['attachments'] = array();
772     
773         $savedLead = $this->_instance->saveLead($lead);
774         $this->assertEquals(0, count($savedLead['attachments']));
775         $this->assertFalse($this->_fsController->fileExists($this->_objects['paths'][0]));
776     }
777     
778     /**
779      * testDeleteLeadWithAttachment
780      * 
781      * @see 0005024: allow to attach external files to records
782      */
783     public function testDeleteLeadWithAttachment()
784     {
785         $lead = $this->testCreateLeadWithAttachment();
786         $this->_instance->deleteLeads(array($lead['id']));
787         $this->assertFalse($this->_fsController->fileExists($this->_objects['paths'][0]));
788     }
789
790     /**
791      * test saving lead with empty start date
792      * 
793      * @see 0009602: CRM should cope with empty start of leads
794      */
795     public function testEmptyStart()
796     {
797         $leadArray = $this->_getLead()->toArray();
798         $leadArray['start'] = null;
799         $newLead = $this->_instance->saveLead($leadArray);
800         
801         $this->assertContains(Tinebase_DateTime::now()->format('Y-m-d'), $newLead['start'], 'start should be set to now if missing');
802     }
803     
804     /**
805      * testSortByLeadState
806      * 
807      * @see 0010792: Sort leads by status and source
808      */
809     public function testSortByLeadState()
810     {
811         $this->_saveLead();
812         $lead2 = $this->_getLead()->toArray();  // open
813         $lead2['leadstate_id'] = 2;             // contacted
814         $this->_instance->saveLead($lead2);
815         
816         $sort = array(
817             'sort' => 'leadstate_id',
818             'dir' => 'ASC'
819         );
820         $searchLeads = $this->_instance->searchLeads($this->_getLeadFilter(), $sort);
821         
822         $this->assertEquals(2, $searchLeads['results'][0]['leadstate_id'], 'leadstate "contacted" should come first');
823     }
824     
825     /**
826      * testAdvancedSearch in related products
827      * 
828      * @see 0010814: quicksearch should search in related records
829      */
830     public function testAdvancedSearchInProduct()
831     {
832         Tinebase_Core::getPreference()->setValue(Tinebase_Preference::ADVANCED_SEARCH, true);
833         
834         $this->_saveLead();
835         $filter = array(
836             array('field' => 'query',           'operator' => 'contains',       'value' => 'PHPUnit test product'),
837         );
838         $searchLeads = $this->_instance->searchLeads($filter, '');
839         $this->assertEquals(1, $searchLeads['totalcount']);
840     }
841 }