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