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