bb1c9475282dbc972141afe2981ac7ffeedf1a37
[tine20] / tests / tine20 / Felamimail / JsonTest.php
1 <?php
2
3 use Sabre\DAV;
4
5 /**
6  * Tine 2.0 - http://www.tine20.org
7  * 
8  * @package     Felamimail
9  * @license     http://www.gnu.org/licenses/agpl.html
10  * @copyright   Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de)
11  * @author      Philipp Schüle <p.schuele@metaways.de>
12  * 
13  */
14
15 /**
16  * Test helper
17  */
18 require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
19
20 /**
21  * Test class for Tinebase_Group
22  */
23 class Felamimail_JsonTest extends PHPUnit_Framework_TestCase
24 {
25     /**
26      * @var Felamimail_Frontend_Json
27      */
28     protected $_json = array();
29
30     /**
31      * message ids to delete
32      *
33      * @var array
34      */
35     protected $_messageIds = array();
36     
37     /**
38      * @var Felamimail_Model_Account
39      */
40     protected $_account = NULL;
41     
42     /**
43      * imap backend
44
45      * @var Felamimail_Backend_ImapProxy
46      */
47     protected $_imap = NULL;
48     
49     /**
50      * name of the folder to use for tests
51      * @var string
52      */
53     protected $_testFolderName = 'Junk';
54     
55     /**
56      * folders to delete in tearDown()
57      * 
58      * @var array
59      */
60     protected $_createdFolders = array();
61     
62     /**
63      * are there messages to delete?
64      * 
65      * @var array
66      */
67     protected $_foldersToClear = array();
68
69     /**
70      * active sieve script name to be restored
71      * 
72      * @var string
73      */
74     protected $_oldActiveSieveScriptName = NULL;
75
76     /**
77      * was sieve_vacation_active ?
78      * 
79      * @var boolean
80      */
81     protected $_oldSieveVacationActiveState = FALSE;
82     
83     /**
84      * old sieve data
85      * 
86      * @var Felamimail_Sieve_Backend_Sql
87      */
88     protected $_oldSieveData = NULL;
89
90     /**
91      * sieve script name to delete
92      * 
93      * @var string
94      */
95     protected $_testSieveScriptName = NULL;
96
97     /**
98      * sieve vacation template file name
99      * 
100      * @var string
101      */
102     protected $_sieveVacationTemplateFile = 'vacation_template.tpl';
103     
104     /**
105      * test email domain
106      * 
107      * @var string
108      */
109     protected $_mailDomain = 'tine20.org';
110     
111     /**
112      * @var Felamimail_Model_Folder
113      */
114     protected $_folder = NULL;
115     
116     /**
117      * paths in the vfs to delete
118      * 
119      * @var array
120      */
121     protected $_pathsToDelete = array();
122     
123     /**
124      * Runs the test methods of this class.
125      *
126      * @access public
127      * @static
128      */
129     public static function main()
130     {
131         $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Felamimail Json Tests');
132         PHPUnit_TextUI_TestRunner::run($suite);
133     }
134
135     /**
136      * Sets up the fixture.
137      * This method is called before a test is executed.
138      *
139      * @access protected
140      */
141     protected function setUp()
142     {
143         Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
144         
145         // get (or create) test accout
146         $this->_account = Felamimail_Controller_Account::getInstance()->search()->getFirstRecord();
147         $this->_oldSieveVacationActiveState = $this->_account->sieve_vacation_active;
148         try {
149             $this->_oldSieveData = new Felamimail_Sieve_Backend_Sql($this->_account);
150         } catch (Tinebase_Exception_NotFound $tenf) {
151             // do nothing
152         }
153         
154         $this->_json = new Felamimail_Frontend_Json();
155         $this->_imap = Felamimail_Backend_ImapFactory::factory($this->_account);
156         
157         foreach (array($this->_testFolderName, $this->_account->sent_folder, $this->_account->trash_folder) as $folderToCreate) {
158             // create folder if it does not exist
159             $this->_getFolder($folderToCreate);
160         }
161         
162         $config = TestServer::getInstance()->getConfig();
163         $this->_mailDomain = ($config->maildomain) ? $config->maildomain : 'tine20.org';
164     }
165
166     /**
167      * Tears down the fixture
168      * This method is called after a test is executed.
169      *
170      * @access protected
171      */
172     protected function tearDown()
173     {
174         if (count($this->_createdFolders) > 0) {
175             foreach ($this->_createdFolders as $folderName) {
176                 //echo "delete $folderName\n";
177                 try {
178                     $this->_imap->removeFolder(Felamimail_Model_Folder::encodeFolderName($folderName));
179                 } catch (Zend_Mail_Storage_Exception $zmse) {
180                     // already deleted
181                 }
182             }
183             Felamimail_Controller_Cache_Folder::getInstance()->clear($this->_account);
184         }
185         
186         if (! empty($this->_foldersToClear)) {
187             foreach ($this->_foldersToClear as $folderName) {
188                 // delete test messages from given folders on imap server (search by special header)
189                 $this->_imap->selectFolder($folderName);
190                 $result = $this->_imap->search(array(
191                     'HEADER X-Tine20TestMessage jsontest'
192                 ));
193                 //print_r($result);
194                 foreach ($result as $messageUid) {
195                     $this->_imap->removeMessage($messageUid);
196                 }
197                 
198                 // clear message cache
199                 $folder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($this->_account->getId(), $folderName);
200                 Felamimail_Controller_Cache_Message::getInstance()->clear($folder);
201             }
202         }
203         
204         // sieve cleanup
205         if ($this->_testSieveScriptName !== NULL) {
206             Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_testSieveScriptName);
207             try {
208                 Felamimail_Controller_Sieve::getInstance()->deleteScript($this->_account->getId());
209             } catch (Zend_Mail_Protocol_Exception $zmpe) {
210                 // do not delete script if active
211             }
212             Felamimail_Controller_Account::getInstance()->setVacationActive($this->_account, $this->_oldSieveVacationActiveState);
213             
214             if ($this->_oldSieveData !== NULL) {
215                 $this->_oldSieveData->save();
216             }
217         }
218         if ($this->_oldActiveSieveScriptName !== NULL) {
219             Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_oldActiveSieveScriptName);
220             Felamimail_Controller_Sieve::getInstance()->activateScript($this->_account->getId());
221         }
222         
223         // vfs cleanup
224         foreach ($this->_pathsToDelete as $path) {
225             $webdavRoot = new DAV\ObjectTree(new Tinebase_WebDav_Root());
226             //echo "delete $path";
227             $webdavRoot->delete($path);
228         }
229         
230         Tinebase_TransactionManager::getInstance()->rollBack();
231     }
232
233     /************************ test functions *********************************/
234     
235     /*********************** folder tests ****************************/
236     
237     /**
238      * test search folders (check order of folders as well)
239      */
240     public function testSearchFolders()
241     {
242         $filter = $this->_getFolderFilter();
243         $result = $this->_json->searchFolders($filter);
244         
245         $this->assertGreaterThan(1, $result['totalcount']);
246         $expectedFolders = array('INBOX', $this->_testFolderName, $this->_account->trash_folder, $this->_account->sent_folder);
247         
248         $foundCount = 0;
249         foreach ($result['results'] as $index => $folder) {
250             if (in_array($folder['localname'], $expectedFolders)) {
251                 $foundCount++;
252             }
253         }
254         $this->assertEquals(count($expectedFolders), $foundCount);
255     }
256     
257     /**
258      * clear test folder
259      */
260     public function testClearFolder()
261     {
262         $folderName = $this->_testFolderName;
263         $folder = $this->_getFolder($this->_testFolderName);
264         $folder = Felamimail_Controller_Folder::getInstance()->emptyFolder($folder->getId());
265
266         $filter = $this->_getMessageFilter($folder->getId());
267         $result = $this->_json->searchMessages($filter, '');
268         
269         $this->assertEquals(0, $result['totalcount'], 'Found too many messages in folder ' . $this->_testFolderName);
270         $this->assertEquals(0, $folder->cache_totalcount);
271     }
272
273     /**
274      * try to create some folders
275      */
276     public function testCreateFolders()
277     {
278         $filter = $this->_getFolderFilter();
279         $result = $this->_json->searchFolders($filter);
280         
281         $foldernames = array('test' => 'test', 'Schlüssel' => 'Schlüssel', 'test//1' => 'test1', 'test\2' => 'test2');
282         
283         foreach ($foldernames as $foldername => $expected) {
284             $result = $this->_json->addFolder($foldername, $this->_testFolderName, $this->_account->getId());
285             $globalname = $this->_testFolderName . $this->_account->delimiter . $expected;
286             $this->_createdFolders[] = $globalname;
287             $this->assertEquals($expected, $result['localname']);
288             $this->assertEquals($globalname, $result['globalname']);
289             $this->assertEquals(Felamimail_Model_Folder::CACHE_STATUS_EMPTY, $result['cache_status']);
290         }
291     }
292     
293     /**
294      * test emtpy folder (with subfolder)
295      */
296     public function testEmptyFolderWithSubfolder()
297     {
298         $folderName = $this->_testFolderName;
299         $folder = $this->_getFolder($this->_testFolderName);
300         $this->testCreateFolders();
301         
302         $folderArray = $this->_json->emptyFolder($folder->getId());
303         $this->assertEquals(0, $folderArray['has_children']);
304         
305         $result = $this->_json->updateFolderCache($this->_account->getId(), $this->_testFolderName);
306         $this->assertEquals(0, count($result));
307     }
308     
309     /**
310      * testUpdateFolderCache
311      */
312     public function testUpdateFolderCache()
313     {
314         $result = $this->_json->updateFolderCache($this->_account->getId(), '');
315         
316         // create folders directly on imap server
317         $this->_imap->createFolder('test', $this->_testFolderName, $this->_account->delimiter);
318         $this->_imap->createFolder('testsub', $this->_testFolderName . $this->_account->delimiter . 'test', $this->_account->delimiter);
319         // if something goes wrong, we need to delete these folders in tearDown
320         $this->_createdFolders[] = $this->_testFolderName . $this->_account->delimiter . 'test' . $this->_account->delimiter . 'testsub';
321         $this->_createdFolders[] = $this->_testFolderName . $this->_account->delimiter . 'test';
322         
323         // update cache and check if folder is found
324         $result = $this->_json->updateFolderCache($this->_account->getId(), $this->_testFolderName);
325         $testfolder = $result[0];
326         //print_r($testfolder);
327         $this->assertGreaterThan(0, count($result));
328         $this->assertEquals($this->_testFolderName . $this->_account->delimiter . 'test', $testfolder['globalname']);
329         $this->assertEquals(TRUE, (bool)$testfolder['has_children'], 'should have children');
330         
331         // delete subfolder directly on imap server
332         $this->_imap->removeFolder($this->_testFolderName . $this->_account->delimiter . 'test' . $this->_account->delimiter . 'testsub');
333         array_shift($this->_createdFolders);
334         
335         // check if has_children got updated and folder is removed from cache
336         $this->_json->updateFolderCache($this->_account->getId(), '');
337         $testfolder = $this->_getFolder($this->_testFolderName . $this->_account->delimiter . 'test');
338         $this->assertEquals(FALSE, (bool)$testfolder['has_children'], 'should have no children');
339         $this->setExpectedException('Tinebase_Exception_NotFound');
340         $testfoldersub = $this->_getFolder($this->_testFolderName . $this->_account->delimiter . 'test' . $this->_account->delimiter . 'testsub');
341
342         $this->_imap->removeFolder($this->_testFolderName . $this->_account->delimiter . 'test');
343         array_shift($this->_createdFolders);
344         
345         // try to update message cache of nonexistant folder
346         $this->setExpectedException('Felamimail_Exception_IMAPFolderNotFound');
347         $removedTestfolder = $this->_json->updateMessageCache($testfolder['id'], 1);
348         
349         // update cache and check if folder is deleted
350         $result = $this->_json->updateFolderCache($this->_account->getId(), $this->_testFolderName);
351         $this->assertEquals(0, count($result));
352     }
353     
354     /*********************** accounts tests **************************/
355     
356     /**
357      * test search for accounts and check default account from config
358      */
359     public function testSearchAccounts()
360     {
361         $system = $this->_getSystemAccount();
362         
363         $this->assertTrue(! empty($system), 'no accounts found');
364         if (TestServer::getInstance()->getConfig()->mailserver) {
365             $this->assertEquals(TestServer::getInstance()->getConfig()->mailserver, $system['host']);
366             $this->assertEquals(TestServer::getInstance()->getConfig()->mailserver, $system['sieve_hostname']);
367         }
368     }
369     
370     /**
371      * get system account
372      * 
373      * @return array
374      */
375     protected function _getSystemAccount()
376     {
377         $results = $this->_json->searchAccounts(array());
378         
379         $this->assertGreaterThan(0, $results['totalcount']);
380         $system = array();
381         foreach ($results['results'] as $result) {
382             if ($result['name'] == Tinebase_Core::getUser()->accountLoginName . '@' . $this->_mailDomain) {
383                 $system = $result;
384             }
385         }
386         
387         return $system;
388     }
389     
390     /**
391      * test change / delete of account
392      */
393     public function testChangeDeleteAccount() 
394     {
395         $system = $this->_getSystemAccount();
396         unset($system['id']);
397         $system['type'] = Felamimail_Model_Account::TYPE_USER;
398         $account = $this->_json->saveAccount($system);
399         
400         $accountRecord = new Felamimail_Model_Account($account, TRUE);
401         $accountRecord->resolveCredentials(FALSE);
402         if (TestServer::getInstance()->getConfig()->mailserver) {
403             $this->assertEquals(TestServer::getInstance()->getConfig()->mailserver, $account['host']);
404         }
405         
406         $this->_json->changeCredentials($account['id'], $accountRecord->user, 'neuespasswort');
407         $account = $this->_json->getAccount($account['id']);
408         
409         $accountRecord = new Felamimail_Model_Account($account, TRUE);
410         $accountRecord->resolveCredentials(FALSE);
411         $this->assertEquals('neuespasswort', $accountRecord->password);
412         
413         $this->_json->deleteAccounts($account['id']);
414     }
415     
416     /*********************** message tests ****************************/
417     
418     /**
419      * test update message cache
420      */
421     public function testUpdateMessageCache()
422     {
423         $message = $this->_sendMessage();
424         $inbox = $this->_getFolder('INBOX');
425         // update message cache and check result
426         $result = $this->_json->updateMessageCache($inbox['id'], 30);
427         
428         if ($result['cache_status'] == Felamimail_Model_Folder::CACHE_STATUS_COMPLETE) {
429             $this->assertEquals($result['imap_totalcount'], $result['cache_totalcount'], 'totalcounts should be equal');
430         } else if ($result['cache_status'] == Felamimail_Model_Folder::CACHE_STATUS_INCOMPLETE) {
431             $this->assertNotEquals(0, $result['cache_job_actions_est']);
432         }
433     }
434     
435     /**
436      * test folder status
437      */
438     public function testGetFolderStatus()
439     {
440         $filter = $this->_getFolderFilter();
441         $result = $this->_json->searchFolders($filter);
442         $this->assertGreaterThan(1, $result['totalcount']);
443         $expectedFolders = array('INBOX', $this->_testFolderName, $this->_account->trash_folder, $this->_account->sent_folder);
444         
445         foreach ($result['results'] as $folder) {
446             $this->_json->updateMessageCache($folder['id'], 30);
447         }
448         
449         $message = $this->_sendMessage();
450         
451         $status = $this->_json->getFolderStatus(array(array('field' => 'account_id', 'operator' => 'equals', 'value' => $this->_account->getId())));
452         $this->assertEquals(1, count($status));
453         $this->assertEquals($this->_account->sent_folder, $status[0]['localname']);
454     }
455
456     /**
457      * test folder status of deleted folder
458      * 
459      * @see 0007134: getFolderStatus should ignore non-existent folders
460      */
461     public function testGetFolderStatusOfDeletedFolder()
462     {
463         $this->testCreateFolders();
464         // remove one of the created folders
465         $removedFolder = $this->_createdFolders[0];
466         $this->_imap->removeFolder(Felamimail_Model_Folder::encodeFolderName($removedFolder));
467         
468         $status = $this->_json->getFolderStatus(array(array('field' => 'account_id', 'operator' => 'equals', 'value' => $this->_account->getId())));
469         $this->assertGreaterThan(2, count($status), 'Expected more than 2 folders that need an update: ' . print_r($status, TRUE));
470         foreach ($status as $folder) {
471             if ($folder['globalname'] == $removedFolder) {
472                 $this->fail('removed folder should not appear in status array!');
473             }
474         }
475     }
476     
477     /**
478      * test send message
479      */
480     public function testSendMessage()
481     {
482         // set email to unittest@tine20.org
483         $contactFilter = new Addressbook_Model_ContactFilter(array(
484             array('field' => 'n_family', 'operator' => 'equals', 'value' => 'Clever')
485         ));
486         $contactIds = Addressbook_Controller_Contact::getInstance()->search($contactFilter, NULL, FALSE, TRUE);
487         $contact = Addressbook_Controller_Contact::getInstance()->get($contactIds[0]);
488         $originalEmail =  $contact->email;
489         $contact->email = $this->_account->email;
490         $contact = Addressbook_Controller_Contact::getInstance()->update($contact, FALSE);
491
492         // send email
493         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain);
494         $messageToSend['note'] = 1;
495         $messageToSend['bcc']  = array(Tinebase_Core::getUser()->accountEmailAddress);
496         //print_r($messageToSend);
497         $returned = $this->_json->saveMessage($messageToSend);
498         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
499         
500         // check if message is in sent folder
501         $message = $this->_searchForMessageBySubject($messageToSend['subject'], $this->_account->sent_folder);
502         $this->assertEquals($message['from_email'], $messageToSend['from_email']);
503         $this->assertTrue(isset($message['to'][0]));
504         $this->assertEquals($message['to'][0],      $messageToSend['to'][0], 'recipient not found');
505         $this->assertEquals($message['bcc'][0],     $messageToSend['bcc'][0], 'bcc recipient not found');
506         $this->assertEquals($message['subject'],    $messageToSend['subject']);
507         
508         $this->_checkEmailNote($contact, $messageToSend['subject']);
509         
510         // reset sclevers original email address
511         $contact->email = $originalEmail;
512         Addressbook_Controller_Contact::getInstance()->update($contact, FALSE);
513     }
514     
515     /**
516      * check email note
517      * 
518      * @param Addressbook_Model_Contact $contact
519      * @param string $subject
520      */
521     protected function _checkEmailNote($contact, $subject)
522     {
523         // check if email note has been added to contact(s)
524         $contact = Addressbook_Controller_Contact::getInstance()->get($contact->getId());
525         $emailNoteType = Tinebase_Notes::getInstance()->getNoteTypeByName('email');
526         
527         // check / delete notes
528         $emailNoteIds = array();
529         foreach ($contact->notes as $note) {
530             if ($note->note_type_id == $emailNoteType->getId()) {
531                 $this->assertContains($subject, $note->note, 'did not find note subject');
532                 $this->assertEquals(Tinebase_Core::getUser()->getId(), $note->created_by);
533                 $this->assertContains('aaaaaä', $note->note);
534                 $emailNoteIds[] = $note->getId();
535             }
536         }
537         $this->assertGreaterThan(0, count($emailNoteIds), 'no email notes found');
538         Tinebase_Notes::getInstance()->deleteNotes($emailNoteIds);
539     }
540
541     /**
542      * test send message to invalid recipient
543      */
544     public function testSendMessageToInvalidRecipient()
545     {
546         $messageToSend = $this->_getMessageData($this->_account->email);
547         $invalidEmail = 'invaliduser@' . $this->_mailDomain;
548         $messageToSend['to'] = array($invalidEmail);
549         
550         try {
551             $returned = $this->_json->saveMessage($messageToSend);
552             $this->fail('Tinebase_Exception_SystemGeneric expected');
553         } catch (Tinebase_Exception_SystemGeneric $tesg) {
554             $this->assertContains('550 5.1.1 <' . $invalidEmail . '>: Recipient address rejected', $tesg->getMessage(),
555                 'exception message did not match: ' . $tesg->getMessage());
556         }
557     }
558     
559     /**
560      * try to get a message from imap server (with complete body, attachments, etc)
561      * 
562      * @see 0006300: add unique message-id header to new messages (for message-id check)
563      */
564     public function testGetMessage()
565     {
566         $message = $this->_sendMessage();
567         
568         // get complete message
569         $message = $this->_json->getMessage($message['id']);
570         
571         // check
572         $this->assertTrue(isset($message['headers']) && $message['headers']['message-id']);
573         $this->assertContains('@' . $this->_mailDomain, $message['headers']['message-id']);
574         $this->assertGreaterThan(0, preg_match('/aaaaaä/', $message['body']));
575         
576         // delete message on imap server and check if correct exception is thrown when trying to get it
577         $this->_imap->selectFolder('INBOX');
578         $this->_imap->removeMessage($message['messageuid']);
579         Tinebase_Core::getCache()->clean();
580         $this->setExpectedException('Felamimail_Exception_IMAPMessageNotFound');
581         $message = $this->_json->getMessage($message['id']);
582     }
583     
584     /**
585      * try to get a message as plain/text
586      */
587     public function testGetPlainTextMessage()
588     {
589         $accountBackend = new Felamimail_Backend_Account();
590         $message = $this->_sendMessage();
591         
592         // get complete message
593         $this->_account->display_format = Felamimail_Model_Account::DISPLAY_PLAIN;
594         $accountBackend->update($this->_account);
595         $message = $this->_json->getMessage($message['id']);
596         $this->_account->display_format = Felamimail_Model_Account::DISPLAY_HTML;
597         $accountBackend->update($this->_account);
598         
599         // check
600         $this->assertEquals("aaaaaä \n\r\n", $message['body']);
601     }
602     
603     /**
604      * try search for a message with path filter
605      */
606     public function testSearchMessageWithPathFilter()
607     {
608         $sentMessage = $this->_sendMessage();
609         $filter = array(array(
610             'field' => 'path', 'operator' => 'in', 'value' => '/' . $this->_account->getId()
611         ));
612         $result = $this->_json->searchMessages($filter, '');
613         $message = $this->_getMessageFromSearchResult($result, $sentMessage['subject']);
614         $this->assertTrue(! empty($message), 'Sent message not found with account path filter');
615
616         $inbox = $this->_getFolder('INBOX');
617         $filter = array(array(
618             'field' => 'path', 'operator' => 'in', 'value' => '/' . $this->_account->getId() . '/' . $inbox->getId()
619         ));
620         $result = $this->_json->searchMessages($filter, '');
621         $message = $this->_getMessageFromSearchResult($result, $sentMessage['subject']);
622         $this->assertTrue(! empty($message), 'Sent message not found with path filter');
623         foreach ($result['results'] as $mail) {
624             $this->assertEquals($inbox->getId(), $mail['folder_id'], 'message is in wrong folder: ' . print_r($mail, TRUE));
625         }
626     }
627     
628     /**
629      * try search for a message with all inboxes and flags filter
630      */
631     public function testSearchMessageWithAllInboxesFilter()
632     {
633         $sentMessage = $this->_sendMessage();
634         $filter = array(
635             array('field' => 'path',  'operator' => 'in',       'value' => Felamimail_Model_MessageFilter::PATH_ALLINBOXES),
636             array('field' => 'flags', 'operator' => 'notin',    'value' => Zend_Mail_Storage::FLAG_FLAGGED),
637         );
638         $result = $this->_json->searchMessages($filter, '');
639         $this->assertGreaterThan(0, $result['totalcount']);
640         $this->assertEquals($result['totalcount'], count($result['results']));
641         
642         $message = $this->_getMessageFromSearchResult($result, $sentMessage['subject']);
643         $this->assertTrue(! empty($message), 'Sent message not found with all inboxes filter');
644     }
645     
646     /**
647      * try search for a message with empty path filter
648      */
649     public function testSearchMessageEmptyPath()
650     {
651         $sentMessage = $this->_sendMessage();
652         
653         $filter = array(
654             array('field' => 'path',  'operator' => 'equals',   'value' => ''),
655         );
656         $result = $this->_json->searchMessages($filter, '');
657         
658         $this->assertEquals(0, $result['totalcount']);
659         $accountFilterFound = FALSE;
660         
661         foreach ($result['filter'] as $filter) {
662             if ($filter['field'] === 'account_id' && empty($filter['value'])) {
663                 $accountFilterFound = TRUE;
664                 break;
665             }
666         }
667         $this->assertTrue($accountFilterFound);
668     }
669     
670     /**
671      * test flags (add + clear + deleted)
672      * 
673      */
674     public function testAddAndClearFlags()
675     {
676         $message = $this->_sendMessage();
677         $inboxBefore = $this->_getFolder('INBOX');
678         
679         $this->_json->addFlags($message['id'], Zend_Mail_Storage::FLAG_SEEN);
680         
681         // check if unread count got decreased
682         $inboxAfter = $this->_getFolder('INBOX');
683         $this->assertTrue($inboxBefore->cache_unreadcount - 1 == $inboxAfter->cache_unreadcount, 'wrong cache unreadcount');
684         
685         $message = $this->_json->getMessage($message['id']);
686         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_SEEN, $message['flags']), 'seen flag not set');
687         
688         // try with a filter
689         $filter = array(
690             array('field' => 'id', 'operator' => 'in', array($message['id']))
691         );
692         $this->_json->clearFlags($filter, Zend_Mail_Storage::FLAG_SEEN);
693         
694         $message = $this->_json->getMessage($message['id']);
695         $this->assertFalse(in_array(Zend_Mail_Storage::FLAG_SEEN, $message['flags']), 'seen flag should not be set');
696
697         $this->setExpectedException('Tinebase_Exception_NotFound');
698         $this->_json->addFlags(array($message['id']), Zend_Mail_Storage::FLAG_DELETED);
699         $this->_json->getMessage($message['id']);
700     }
701
702     /**
703      * test delete from trash
704      */
705     public function testDeleteFromTrashWithFilter()
706     {
707         $message = $this->_sendMessage();
708         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_account->trash_folder);
709         
710         $trash = $this->_getFolder($this->_account->trash_folder);
711         $result = $this->_json->moveMessages(array(array(
712             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
713         )), $trash->getId());
714
715         $messageInTrash = $this->_searchForMessageBySubject($message['subject'], $this->_account->trash_folder);
716         
717         // delete messages in trash with filter
718         $this->_json->addFlags(array(array(
719             'field' => 'folder_id', 'operator' => 'equals', 'value' => $trash->getId()
720         ), array(
721             'field' => 'id', 'operator' => 'in', 'value' => array($messageInTrash['id'])
722         )), Zend_Mail_Storage::FLAG_DELETED);
723         
724         $this->setExpectedException('Tinebase_Exception_NotFound');
725         $this->_json->getMessage($messageInTrash['id']);
726     }
727     
728     /**
729      * move message to trash with trash folder constant (Felamimail_Model_Folder::FOLDER_TRASH)
730      */
731     public function testMoveMessagesToTrash()
732     {
733         $message = $this->_sendMessage();
734         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_account->trash_folder);
735         
736         $result = $this->_json->moveMessages(array(array(
737             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
738         )), Felamimail_Model_Folder::FOLDER_TRASH);
739
740         $messageInTrash = $this->_searchForMessageBySubject($message['subject'], $this->_account->trash_folder);
741     }
742     
743     /**
744      * test reply mail and check some headers
745      * 
746      * @see 0006106: Add References header / https://forge.tine20.org/mantisbt/view.php?id=6106
747      */
748     public function testReplyMessage()
749     {
750         $message = $this->_sendMessage();
751         
752         $replyMessage = $this->_getReply($message);
753         $returned = $this->_json->saveMessage($replyMessage);
754         
755         $result = $this->_getMessages();
756         
757         $replyMessageFound = array();
758         $originalMessage = array();
759         foreach ($result['results'] as $mail) {
760             if ($mail['subject'] == $replyMessage['subject']) {
761                 $replyMessageFound = $mail;
762             }
763             if ($mail['subject'] == $message['subject']) {
764                 $originalMessage = $mail;
765             }
766         }
767         $replyMessageFound = $this->_json->getMessage($replyMessageFound['id']);
768         $originalMessage = $this->_json->getMessage($originalMessage['id']);
769         
770         $this->assertTrue(! empty($replyMessageFound), 'replied message not found');
771         $this->assertTrue(! empty($originalMessage), 'original message not found');
772         
773         // check headers
774         $this->assertTrue(isset($replyMessageFound['headers']['in-reply-to']));
775         $this->assertEquals($originalMessage['headers']['message-id'], $replyMessageFound['headers']['in-reply-to']);
776         $this->assertTrue(isset($replyMessageFound['headers']['references']));
777         $this->assertEquals($originalMessage['headers']['message-id'], $replyMessageFound['headers']['references']);
778         
779         // check answered flag
780         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_ANSWERED, $originalMessage['flags'], 'could not find flag'));
781     }
782     
783     /**
784      * get reply message data
785      * 
786      * @param array $_original
787      * @return array
788      */
789     protected function _getReply($_original)
790     {
791         $replyMessage               = $this->_getMessageData();
792         $replyMessage['subject']    = 'Re: ' . $_original['subject'];
793         $replyMessage['original_id']= $_original['id'];
794         $replyMessage['flags']      = Zend_Mail_Storage::FLAG_ANSWERED;
795         
796         return $replyMessage;
797     }
798
799     /**
800      * test reply mail in sent folder
801      */
802     public function testReplyMessageInSentFolder()
803     {
804         $messageInSent = $this->_sendMessage($this->_account->sent_folder);
805         $replyMessage = $this->_getReply($messageInSent);
806         $returned = $this->_json->saveMessage($replyMessage);
807         
808         $result = $this->_getMessages();
809         $sentMessage = $this->_getMessageFromSearchResult($result, $replyMessage['subject']);
810         $this->assertTrue(! empty($sentMessage));
811     }
812
813     /**
814      * test reply mail with long references header
815      * 
816      * @see 0006644: "At least one mail header line is too long"
817      */
818     public function testReplyMessageWithLongHeader()
819     {
820         $messageInSent = $this->_sendMessage($this->_account->sent_folder, array(
821             'references' => '<c95d8187-2c71-437e-adb8-5e1dcdbdc507@email.test.org>
822    <2601bbfa-566e-4490-a3db-aad005733d32@email.test.org>
823    <20120530154350.1854610131@ganymed.de>
824    <7e393ce1-d193-44fc-bf5f-30c61a271fe6@email.test.org>
825    <4FC8B49C.8040704@funk.de>
826    <dba2ad5c-6726-4171-8710-984847c010a1@email.test.org>
827    <20120601123551.5E98610131@ganymed.de>
828    <f1cc3195-8641-46e3-8f20-f60f3e16b107@email.test.org>
829    <20120619093658.37E4210131@ganymed.de>
830    <CA+6Rn2PX2Q3tOk2tCQfCjcaC8zYS5XZX327OoyJfUb+w87vCLQ@mail.net.com>
831    <20120619130652.03DD310131@ganymed.de>
832    <37616c6a-4c47-4b54-9ca6-56875bc9205d@email.test.org>
833    <20120620074843.42E2010131@ganymed.de>
834    <CA+6Rn2MAb2x0qeSfcaW6F=0S7LEQL442Sx2ha9RtwMs4B0esBg@mail.net.com>
835    <20120620092902.88C8C10131@ganymed.de>
836    <c95d8187-2c71-437e-adb8-5e1dcdbdc507@email.test.org>
837    <2601bbfa-566e-4490-a3db-aad005733d32@email.test.org>
838    <20120530154350.1854610131@ganymed.de>
839    <7e393ce1-d193-44fc-bf5f-30c61a271fe6@email.test.org>
840    <4FC8B49C.8040704@funk.de>
841    <dba2ad5c-6726-4171-8710-984847c010a1@email.test.org>
842    <20120601123551.5E98610131@ganymed.de>
843    <f1cc3195-8641-46e3-8f20-f60f3e16b107@email.test.org>
844    <20120619093658.37E4210131@ganymed.de>
845    <CA+6Rn2PX2Q3tOk2tCQfCjcaC8zYS5XZX327OoyJfUb+w87vCLQ@mail.net.com>
846    <20120619130652.03DD310131@ganymed.de>
847    <37616c6a-4c47-4b54-9ca6-56875bc9205d@email.test.org>
848    <20120620074843.42E2010131@ganymed.de>
849    <CA+6Rn2MAb2x0qeSfcaW6F=0S7LEQL442Sx2ha9RtwMs4B0esBg@mail.net.com>
850    <20120620092902.88C8C10131@ganymed.de>'
851         ));
852         $replyMessage = $this->_getReply($messageInSent);
853         $returned = $this->_json->saveMessage($replyMessage);
854         
855         $result = $this->_getMessages();
856         $sentMessage = $this->_getMessageFromSearchResult($result, $replyMessage['subject']);
857         $this->assertTrue(! empty($sentMessage));
858     }
859     
860     /**
861      * test move
862      */
863     public function testMoveMessage()
864     {
865         $message = $this->_sendMessage();
866         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_testFolderName);
867         
868         $inbox = $this->_getFolder('INBOX');
869         $inboxBefore = $this->_json->updateMessageCache($inbox['id'], 30);
870         
871         // move
872         $testFolder = $this->_getFolder($this->_testFolderName);
873         $result = $this->_json->moveMessages(array(array(
874             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
875         )), $testFolder->getId());
876
877         $inboxAfter = $this->_getFolder('INBOX');
878         
879         // check if count was decreased correctly
880         $this->assertEquals($inboxBefore['cache_unreadcount'] - 1, $inboxAfter['cache_unreadcount']);
881         $this->assertEquals($inboxBefore['cache_totalcount'] - 1, $inboxAfter['cache_totalcount']);
882         
883         $result = $this->_getMessages($this->_testFolderName);
884         $movedMessage = array();
885         foreach ($result['results'] as $mail) {
886             if ($mail['subject'] == $message['subject']) {
887                 $movedMessage = $mail;
888             }
889         }
890         $this->assertTrue(! empty($movedMessage), 'moved message not found');
891     }
892     
893     /**
894      * forward message test
895      * 
896      * @see 0007624: losing umlauts in attached filenames
897      */
898     public function testForwardMessageWithAttachment()
899     {
900         $testFolder = $this->_getFolder($this->_testFolderName);
901         $message = fopen(dirname(__FILE__) . '/files/multipart_related.eml', 'r');
902         Felamimail_Controller_Message::getInstance()->appendMessage($testFolder, $message);
903         
904         $subject = 'Tine 2.0 bei Metaways - Verbessurngsvorschlag';
905         $message = $this->_searchForMessageBySubject($subject, $this->_testFolderName);
906         
907         $fwdSubject = 'Fwd: ' . $subject;
908         $forwardMessageData = array(
909             'account_id'    => $this->_account->getId(),
910             'subject'       => $fwdSubject,
911             'to'            => array('unittest@' . $this->_mailDomain),
912             'body'          => "aaaaaä <br>",
913             'headers'       => array('X-Tine20TestMessage' => 'jsontest'),
914             'original_id'   => $message['id'],
915             'attachments'   => array(new Tinebase_Model_TempFile(array(
916                 'type'  => Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822,
917                 'name'  => 'Verbessurüngsvorschlag',
918             ), TRUE)),
919             'flags'         => Zend_Mail_Storage::FLAG_PASSED,
920         );
921         
922         $this->_foldersToClear[] = 'INBOX';
923         $this->_json->saveMessage($forwardMessageData);
924         $forwardMessage = $this->_searchForMessageBySubject($fwdSubject);
925         
926         // check attachment name
927         $forwardMessageComplete = $this->_json->getMessage($forwardMessage['id']);
928         $this->assertEquals(1, count($forwardMessageComplete['attachments']));
929         $this->assertEquals('Verbessurüngsvorschlag.eml', $forwardMessageComplete['attachments'][0]['filename'], 'umlaut missing from attachment filename');
930         
931         $forwardMessage = $this->_json->getMessage($forwardMessage['id']);
932         $this->assertTrue((isset($forwardMessage['structure']) || array_key_exists('structure', $forwardMessage)), 'structure should be set when fetching complete message: ' . print_r($forwardMessage, TRUE));
933         $this->assertEquals(Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822, $forwardMessage['structure']['parts'][2]['contentType']);
934         
935         $message = $this->_json->getMessage($message['id']);
936         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_PASSED, $message['flags']), 'forwarded flag missing in flags: ' . print_r($message, TRUE));
937     }
938     
939     /**
940      * testSendMessageWithAttachmentWithoutExtension
941      * 
942      * @see 0008328: email attachment without file extension is not sent properly
943      */
944     public function testSendMessageWithAttachmentWithoutExtension()
945     {
946         $subject = 'attachment test';
947         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain, $subject);
948         $tempfileName = 'jsontest' . Tinebase_Record_Abstract::generateUID(10);
949         $tempfilePath = Tinebase_Core::getTempDir() . DIRECTORY_SEPARATOR . $tempfileName;
950         file_put_contents($tempfilePath, 'some content');
951         $tempFile = Tinebase_TempFile::getInstance()->createTempFile($tempfilePath, $tempfileName);
952         $messageToSend['attachments'] = array(array('tempFile' => array('id' => $tempFile->getId())));
953         $this->_json->saveMessage($messageToSend);
954         $forwardMessage = $this->_searchForMessageBySubject($subject);
955         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
956         
957         $fullMessage = $this->_json->getMessage($forwardMessage['id']);
958         $this->assertTrue(count($fullMessage['attachments']) === 1);
959         $attachment = $fullMessage['attachments'][0];
960         $this->assertContains($tempfileName, $attachment['filename'], 'wrong attachment filename: ' . print_r($attachment, TRUE));
961         $this->assertEquals(16, $attachment['size'], 'wrong attachment size: ' . print_r($attachment, TRUE));
962     }
963     
964     /**
965      * save message in folder (draft) test
966      * 
967      * @see 0007178: BCC does not save the draft message
968      */
969     public function testSaveMessageInFolder()
970     {
971         $messageToSave = $this->_getMessageData();
972         $messageToSave['bcc'] = array('bccaddress@email.org', 'bccaddress2@email.org');
973         
974         $draftsFolder = $this->_getFolder($this->_account->drafts_folder);
975         $returned = $this->_json->saveMessageInFolder($this->_account->drafts_folder, $messageToSave);
976         $this->_foldersToClear = array($this->_account->drafts_folder);
977         
978         // check if message is in drafts folder and recipients are present
979         $message = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_account->drafts_folder);
980         $this->assertEquals($messageToSave['subject'],  $message['subject']);
981         $this->assertEquals($messageToSave['to'][0],    $message['to'][0], 'recipient not found');
982         $this->assertEquals(2, count($message['bcc']), 'bcc recipient not found: ' . print_r($message, TRUE));
983         $this->assertEquals($messageToSave['bcc'][0],   $message['bcc'][0], '1st bcc recipient not found');
984         $this->assertEquals($messageToSave['bcc'][1],   $message['bcc'][1], '2nd bcc recipient not found');
985     }
986     
987     /**
988      * testSendReadingConfirmation
989      * 
990      * @see 0007736: ask user before sending reading confirmation
991      * @see 0008402: Wrong recipient with read confirmation
992      */
993     public function testSendReadingConfirmation()
994     {
995         $messageToSave = $this->_getMessageData();
996         $messageToSave['headers']['disposition-notification-to'] = '"' . Tinebase_Core::getUser()->accountFullName . '" <' . $this->_account->email . '>';
997         $returned = $this->_json->saveMessageInFolder($this->_testFolderName, $messageToSave);
998         $messageWithReadingConfirmationHeader = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_testFolderName);
999         $this->_messageIds[] = $messageWithReadingConfirmationHeader['id'];
1000         $this->_json->sendReadingConfirmation($messageWithReadingConfirmationHeader['id']);
1001         
1002         $translate = Tinebase_Translation::getTranslation('Felamimail');
1003         $subject = $translate->_('Reading Confirmation:') . ' '. $messageToSave['subject'];
1004         $message = $this->_searchForMessageBySubject($subject);
1005         $this->_messageIds[] = $message['id'];
1006         
1007         $complete = $this->_json->getMessage($message['id']);
1008         $this->assertContains($translate->_('Was read by:') . ' ' . $this->_account->from, $complete['body']);
1009     }
1010
1011     /**
1012      * save message in non-existant folder (templates) test
1013      * 
1014      * @see 0008476: Drafts are not working
1015      */
1016     public function testSaveMessageInNonExistantTemplatesFolder()
1017     {
1018         $messageToSave = $this->_getMessageData();
1019         
1020         $templatesFolder = $this->_getFolder($this->_account->templates_folder, FALSE);
1021         if ($templatesFolder) {
1022             $this->_json->deleteFolder($templatesFolder['id'], $this->_account->getId());
1023         }
1024         $returned = $this->_json->saveMessageInFolder($this->_account->templates_folder, $messageToSave);
1025         $this->_foldersToClear = array($this->_account->templates_folder);
1026         
1027         // check if message is in templates folder
1028         $message = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_account->templates_folder);
1029         $this->assertEquals($messageToSave['subject'],  $message['subject']);
1030         $this->assertEquals($messageToSave['to'][0],    $message['to'][0], 'recipient not found');
1031     }
1032     
1033     /**
1034      * testSaveMessageNoteWithInvalidChar
1035      * 
1036      * @see 0008644: error when sending mail with note (wrong charset)
1037      */
1038     public function testSaveMessageNoteWithInvalidChar()
1039     {
1040         $subject = Tinebase_Core::filterInputForDatabase("\xF0\x9F\x98\x8A\xC2"); // :-) emoji &nbsp;
1041         $messageData = $this->_getMessageData('', $subject);
1042         $messageData['note'] = true;
1043         $messageData['body'] .= "&nbsp;";
1044         
1045         $this->_foldersToClear[] = 'INBOX';
1046         $this->_json->saveMessage($messageData);
1047         $message = $this->_searchForMessageBySubject($subject);
1048         
1049         $contact = Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
1050         $this->_checkEmailNote($contact, $subject);
1051     }
1052     
1053     /**
1054      * testSaveMessageNoteWithInvalidChar
1055      * 
1056      * @see 0008644: error when sending mail with note (wrong charset)
1057      */
1058     public function testSaveMessageWithInvalidChar()
1059     {
1060         $subject = "\xF0\x9F\x98\x8A"; // :-) emoji
1061         $messageData = $this->_getMessageData('', $subject);
1062         $this->_foldersToClear[] = 'INBOX';
1063         $this->_json->saveMessage($messageData);
1064         $message = $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
1065     }
1066     
1067     /**
1068      * testMessageWithInvalidICS
1069      * 
1070      * @see 0008786: broken ics causes js error when showing details
1071      */
1072     public function testMessageWithInvalidICS()
1073     {
1074         $inbox = $this->_getFolder('INBOX');
1075         $mailAsString = file_get_contents(dirname(__FILE__) . '/files/invalidimip.eml');
1076         Felamimail_Controller_Message::getInstance()->appendMessage($inbox, $mailAsString);
1077         
1078         $this->_foldersToClear = array('INBOX');
1079         $message = $this->_searchForMessageBySubject('test invalid imip');
1080         
1081         $fullMessage = $this->_json->getMessage($message['id']);
1082         $this->assertTrue(empty($fullMessage->preparedParts));
1083     }
1084     
1085     /*********************** sieve tests ****************************/
1086     
1087     /**
1088      * set and get vacation sieve script
1089      * 
1090      * @see 0007768: Sieve - Vacation notify frequency not being set (Cyrus)
1091      */
1092     public function testGetSetVacation()
1093     {
1094         $vacationData = $this->_getVacationData();
1095         $this->_sieveTestHelper($vacationData);
1096         
1097         // check if script was activated
1098         $activeScriptName = Felamimail_Controller_Sieve::getInstance()->getActiveScriptName($this->_account->getId());
1099         $this->assertEquals($this->_testSieveScriptName, $activeScriptName);
1100         $updatedAccount = Felamimail_Controller_Account::getInstance()->get($this->_account->getId());
1101         $this->assertTrue((bool) $updatedAccount->sieve_vacation_active);
1102         
1103         $result = $this->_json->getVacation($this->_account->getId());
1104
1105         $this->assertEquals($this->_account->email, $result['addresses'][0]);
1106         
1107         $sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1108         if (preg_match('/dbmail/i', $sieveBackend->getImplementation())) {
1109             $translate = Tinebase_Translation::getTranslation('Felamimail');
1110             $vacationData['subject'] = sprintf($translate->_('Out of Office reply from %1$s'), Tinebase_Core::getUser()->accountFullName);
1111         }
1112         
1113         foreach (array('reason', 'enabled', 'subject', 'from', 'days') as $field) {
1114             $this->assertEquals($vacationData[$field], $result[$field], 'vacation data mismatch: ' . $field);
1115         }
1116     }
1117     
1118     /**
1119      * get vacation data
1120      * 
1121      * @return array
1122      */
1123     protected function _getVacationData()
1124     {
1125         return array(
1126             'id'                    => $this->_account->getId(),
1127             'subject'               => 'unittest vacation subject',
1128             'from'                  => $this->_account->from . ' <' . $this->_account->email . '>',
1129             'days'                  => 3,
1130             'enabled'               => TRUE,
1131             'reason'                => 'unittest vacation message<br /><br />signature',
1132             'mime'                  => NULL,
1133         );
1134     }
1135     
1136     /**
1137      * test mime vacation sieve script
1138      */
1139     public function testMimeVacation()
1140     {
1141         $vacationData = $this->_getVacationData();
1142         $vacationData['reason'] = "\n<html><body><h1>unittest vacation&nbsp;message</h1></body></html>";
1143         
1144         $_sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1145         if (! in_array('mime', $_sieveBackend->capability())) {
1146             $vacationData['mime'] = 'text/html';
1147         }
1148         
1149         $this->_sieveTestHelper($vacationData, TRUE);
1150     }
1151     
1152     /**
1153      * test get/set of rules sieve script
1154      */
1155     public function testGetSetRules()
1156     {
1157         $ruleData = $this->_getRuleData();
1158         
1159         $this->_sieveTestHelper($ruleData);
1160         
1161         // check getRules
1162         $result = $this->_json->getRules($this->_account->getId());
1163         $this->assertEquals($result['totalcount'], count($ruleData));
1164         
1165         // check by sending mail
1166         $messageData = $this->_getMessageData('', 'viagra');
1167         $returned = $this->_json->saveMessage($messageData);
1168         $this->_foldersToClear = array('INBOX', $this->_testFolderName);
1169         // check if message is in test folder
1170         $message = $this->_searchForMessageBySubject($messageData['subject'], $this->_testFolderName);
1171     }
1172     
1173     /**
1174      * testRemoveRules
1175      * 
1176      * @see 0006490: can not delete single filter rule
1177      */
1178     public function testRemoveRules()
1179     {
1180         $this->testGetSetRules();
1181         $this->_json->saveRules($this->_account->getId(), array());
1182         
1183         $result = $this->_json->getRules($this->_account->getId());
1184         $this->assertEquals(0, $result['totalcount'], 'found rules: ' . print_r($result, TRUE));
1185     }
1186     
1187     /**
1188      * get sieve rule data
1189      * 
1190      * @return array
1191      */
1192     protected function _getRuleData()
1193     {
1194         return array(array(
1195             'id'            => 1,
1196             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO, 
1197             'action_argument' => $this->_testFolderName,
1198             'conjunction'  => 'allof',
1199             'conditions'    => array(array(
1200                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1201                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1202                 'header'        => 'From',
1203                 'key'           => '"abcd" <info@example.org>',
1204             )),
1205             'enabled'       => 1,
1206         ), array(
1207             'id'            => 2,
1208             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO, 
1209             'action_argument' => $this->_testFolderName,
1210             'conjunction'  => 'allof',
1211             'conditions'    => array(array(
1212                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1213                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1214                 'header'        => 'From',
1215                 'key'           => 'info@example.org',
1216             )),
1217             'enabled'       => 0,
1218         ), array(
1219             'id'            => 3,
1220             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO, 
1221             'action_argument' => $this->_testFolderName,
1222             'conjunction'  => 'allof',
1223             'conditions'    => array(array(
1224                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_HEADER,
1225                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_REGEX,
1226                 'header'        => 'subject',
1227                 'key'           => '[vV]iagra|cyalis',
1228             )),
1229             'enabled'       => 1,
1230         ));
1231     }
1232     
1233     /**
1234      * test to set a forward rule to this accounts email address
1235      * -> should throw exception to prevent mail cycling
1236      */
1237     public function testSetForwardRuleToSelf()
1238     {
1239         $ruleData = array(array(
1240             'id'            => 1,
1241             'action_type'   => Felamimail_Sieve_Rule_Action::REDIRECT, 
1242             'action_argument' => $this->_account->email,
1243             'conjunction'     => 'allof',
1244             'conditions'    => array(array(
1245                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1246                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1247                 'header'        => 'From',
1248                 'key'           => 'info@example.org',
1249             )),
1250             'enabled'       => 1,
1251         ));
1252         
1253         try {
1254             $this->_sieveTestHelper($ruleData);
1255             $this->assertTrue(FALSE, 'it is not allowed to set own email address for redirect!');
1256         } catch (Felamimail_Exception_Sieve $e) {
1257             $this->assertTrue(TRUE);
1258         }
1259
1260         // this should work
1261         $ruleData[0]['enabled'] = 0;
1262         $this->_sieveTestHelper($ruleData);
1263     }
1264     
1265     /**
1266      * testGetVacationTemplates
1267      * 
1268      * @return array
1269      */
1270     public function testGetVacationTemplates()
1271     {
1272         $this->_addVacationTemplateFile();
1273         $result = $this->_json->getVacationMessageTemplates();
1274         
1275         $this->assertTrue($result['totalcount'] > 0, 'no templates found');
1276         $found = FALSE;
1277         foreach ($result['results'] as $template) {
1278             if ($template['name'] === $this->_sieveVacationTemplateFile) {
1279                 $found = TRUE;
1280                 break;
1281             }
1282         }
1283         
1284         $this->assertTrue($found, 'wrong templates: ' . print_r($result['results'], TRUE));
1285         
1286         return $template;
1287     }
1288     
1289     /**
1290      * add vacation template file to vfs
1291      */
1292     protected function _addVacationTemplateFile()
1293     {
1294         $webdavRoot = new DAV\ObjectTree(new Tinebase_WebDav_Root());
1295         $path = '/webdav/Felamimail/shared/Vacation Templates';
1296         $node = $webdavRoot->getNodeForPath($path);
1297         $this->_pathsToDelete[] = $path . '/' . $this->_sieveVacationTemplateFile;
1298         $node->createFile($this->_sieveVacationTemplateFile, fopen(dirname(__FILE__) . '/files/' . $this->_sieveVacationTemplateFile, 'r'));
1299     }
1300     
1301     /**
1302      * testGetVacationMessage
1303      */
1304     public function testGetVacationMessage()
1305     {
1306         $result = $this->_getVacationMessageWithTemplate();
1307         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1308         $this->assertEquals("Ich bin vom 18.04.2012 bis zum 20.04.2012 im Urlaub. Bitte kontaktieren Sie<br /> Paul Wulf (pwulf@tine20.org) oder Susan Clever (" .
1309             $sclever->accountEmailAddress . ").<br /><br />I am on vacation until Apr 20, 2012. Please contact Paul Wulf<br />(pwulf@tine20.org) or Susan Clever (" .
1310             $sclever->accountEmailAddress . ") instead.<br /><br />" .
1311             Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId())->n_fn, $result['message']);
1312     }
1313     
1314     /**
1315      * get vacation message with template
1316      * 
1317      * @return array
1318      */
1319     protected function _getVacationMessageWithTemplate()
1320     {
1321         $template = $this->testGetVacationTemplates();
1322         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1323         $result = $this->_json->getVacationMessage(array(
1324             'start_date' => '2012-04-18',
1325             'end_date'   => '2012-04-20',
1326             'contact_ids' => array(
1327                 Tinebase_User::getInstance()->getFullUserByLoginName('pwulf')->contact_id,
1328                 $sclever->contact_id,
1329             ),
1330             'template_id' => $template['id'],
1331             'signature' => $this->_account->signature
1332         ));
1333         
1334         return $result;
1335     }
1336     
1337     /**
1338      * testGetVacationWithSignature
1339      * 
1340      * @see 0006866: check signature linebreaks in vacation message from template
1341      */
1342     public function testGetVacationWithSignature()
1343     {
1344         $this->_sieveVacationTemplateFile = 'vacation_template_sig.tpl';
1345         
1346         // set signature with <br> + linebreaks
1347         $this->_account->signature = "llalala<br>\nxyz<br>\nblubb<br>";
1348         
1349         $result = $this->_getVacationMessageWithTemplate();
1350         $this->assertContains('-- <br />llalala<br />xyz<br />blubb<br />', $result['message'], 'wrong linebreaks or missing signature');
1351     }
1352     
1353     /**
1354     * testSetVacationWithStartAndEndDate
1355     *
1356     * @see 0006266: automatic deactivation of vacation message
1357     */
1358     public function testSetVacationWithStartAndEndDate()
1359     {
1360         $vacationData = $this->_getVacationData();
1361         $vacationData['start_date'] = '2012-04-18';
1362         $vacationData['end_date'] = '2012-04-20';
1363         $result = $this->_sieveTestHelper($vacationData);
1364         
1365         $this->assertContains($vacationData['start_date'], $result['start_date']);
1366         $this->assertContains($vacationData['end_date'], $result['end_date']);
1367     }
1368     
1369     /**
1370      * testSieveRulesOrder
1371      * 
1372      * @see 0007240: order of sieve rules changes when vacation message is saved
1373      */
1374     public function testSieveRulesOrder()
1375     {
1376         $this->_setTestScriptname();
1377         
1378         // disable vacation first
1379         $this->_setDisabledVacation();
1380         
1381         $sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1382         
1383         $ruleData = $this->_getRuleData();
1384         $ruleData[0]['id'] = $ruleData[2]['id'];
1385         $ruleData[2]['id'] = 11;
1386         $resultSet = $this->_json->saveRules($this->_account->getId(), $ruleData);
1387         $sieveScriptRules = $sieveBackend->getScript($this->_testSieveScriptName);
1388         
1389         $this->_setDisabledVacation();
1390         $sieveScriptVacation = $sieveBackend->getScript($this->_testSieveScriptName);
1391         
1392         // compare sieve scripts
1393         $this->assertContains($sieveScriptRules, $sieveScriptVacation, 'rule order changed');
1394     }
1395     
1396     /**
1397      * use another name for test sieve script
1398      */
1399     protected function _setTestScriptname()
1400     {
1401         $this->_oldActiveSieveScriptName = Felamimail_Controller_Sieve::getInstance()->getActiveScriptName($this->_account->getId());
1402         $this->_testSieveScriptName = 'Felamimail_Unittest';
1403         Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_testSieveScriptName);
1404     }
1405     
1406     /**
1407      * set disabled vacation message
1408      */
1409     protected function _setDisabledVacation()
1410     {
1411         $vacationData = $this->_getVacationData();
1412         $vacationData['enabled'] = FALSE;
1413         $resultSet = $this->_json->saveVacation($vacationData);
1414     }
1415     
1416     /**
1417      * get folder filter
1418      *
1419      * @return array
1420      */
1421     protected function _getFolderFilter()
1422     {
1423         return array(array(
1424             'field' => 'globalname', 'operator' => 'equals', 'value' => ''
1425         ));
1426     }
1427
1428     /**
1429      * get message filter
1430      *
1431      * @param string $_folderId
1432      * @return array
1433      */
1434     protected function _getMessageFilter($_folderId)
1435     {
1436         $result = array(array(
1437             'field' => 'folder_id', 'operator' => 'equals', 'value' => $_folderId
1438         ));
1439         
1440         return $result;
1441     }
1442     
1443     /**
1444      * get mailbox
1445      *
1446      * @param string $name
1447      * @param boolean $createFolder
1448      * @return Felamimail_Model_Folder|NULL
1449      */
1450     protected function _getFolder($name, $createFolder = TRUE)
1451     {
1452         Felamimail_Controller_Cache_Folder::getInstance()->update($this->_account->getId());
1453         try {
1454             $folder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($this->_account->getId(), $name);
1455         } catch (Tinebase_Exception_NotFound $tenf) {
1456             $folder = ($createFolder) ? Felamimail_Controller_Folder::getInstance()->create($this->_account, $name) : NULL;
1457         }
1458         
1459         return $folder;
1460     }
1461
1462     /**
1463      * get message data
1464      *
1465      * @return array
1466      */
1467     protected function _getMessageData($_emailFrom = '', $_subject = 'test')
1468     {
1469         return array(
1470             'account_id'    => $this->_account->getId(),
1471             'subject'       => $_subject,
1472             'to'            => array(Tinebase_Core::getUser()->accountEmailAddress),
1473             'body'          => 'aaaaaä <br>',
1474             'headers'       => array('X-Tine20TestMessage' => 'jsontest'),
1475             'from_email'    => $_emailFrom,
1476             'content_type'  => Felamimail_Model_Message::CONTENT_TYPE_HTML,
1477         );
1478     }
1479
1480     /**
1481      * send message and return message array
1482      *
1483      * @param string $folderName
1484      * @param array $addtionalHeaders
1485      * @return array
1486      */
1487     protected function _sendMessage($folderName = 'INBOX', $addtionalHeaders = array())
1488     {
1489         $messageToSend = $this->_getMessageData();
1490         $messageToSend['headers'] = array_merge($messageToSend['headers'], $addtionalHeaders);
1491         $returned = $this->_json->saveMessage($messageToSend);
1492         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
1493         
1494         $result = $this->_getMessages($folderName);
1495         $message = $this->_getMessageFromSearchResult($result, $messageToSend['subject']);
1496         
1497         $this->assertTrue(! empty($message), 'Sent message not found.');
1498         
1499         return $message;
1500     }
1501     
1502     /**
1503      * returns message array from result
1504      * 
1505      * @param array $_result
1506      * @param string $_subject
1507      * @return array
1508      */
1509     protected function _getMessageFromSearchResult($_result, $_subject)
1510     {
1511         $message = array();
1512         foreach ($_result['results'] as $mail) {
1513             if ($mail['subject'] == $_subject) {
1514                 $message = $mail;
1515             }
1516         }
1517         
1518         return $message;
1519     }
1520     
1521     /**
1522      * get messages from folder
1523      * 
1524      * @param string $_folderName
1525      * @return array
1526      */
1527     protected function _getMessages($_folderName = 'INBOX')
1528     {
1529         $folder = $this->_getFolder($_folderName);
1530         $filter = $this->_getMessageFilter($folder->getId());
1531         // update cache
1532         $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10, 1);
1533         $i = 0;
1534         while ($folder->cache_status != Felamimail_Model_Folder::CACHE_STATUS_COMPLETE && $i < 10) {
1535             $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10);
1536             $i++;
1537         }
1538         $result = $this->_json->searchMessages($filter, '');
1539         //print_r($result);
1540         
1541         return $result;
1542     }
1543     
1544     /**
1545      * search for message defined by subject in folder
1546      * 
1547      * @param string $_subject
1548      * @param string $_folderName
1549      * @return string message data
1550      */
1551     protected function _searchForMessageBySubject($_subject, $_folderName = 'INBOX')
1552     {
1553         $result = $this->_getMessages($_folderName);
1554         
1555         $message = array();
1556         foreach ($result['results'] as $mail) {
1557             if ($mail['subject'] == $_subject) {
1558                 $message = $mail;
1559             }
1560         }
1561         $this->assertGreaterThan(0, $result['totalcount'], 'folder is empty');
1562         $this->assertTrue(! empty($message), 'Message not found');
1563         
1564         return $message;
1565     }
1566     
1567     /**
1568      * sieve test helper
1569      * 
1570      * @param array $_sieveData
1571      * @return array
1572      */
1573     protected function _sieveTestHelper($_sieveData, $_isMime = FALSE)
1574     {
1575         $this->_setTestScriptname();
1576         
1577         // check which save fn to use
1578         if ((isset($_sieveData['reason']) || array_key_exists('reason', $_sieveData))) {
1579             $resultSet = $this->_json->saveVacation($_sieveData);
1580             $this->assertEquals($this->_account->email, $resultSet['addresses'][0]);
1581             
1582             $_sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1583             
1584             if (preg_match('/dbmail/i', $_sieveBackend->getImplementation())) {
1585                 $translate = Tinebase_Translation::getTranslation('Felamimail');
1586                 $this->assertEquals(sprintf(
1587                     $translate->_('Out of Office reply from %1$s'), Tinebase_Core::getUser()->accountFullName), 
1588                     $resultSet['subject']
1589                 );
1590             } else {
1591                 $this->assertEquals($_sieveData['subject'], $resultSet['subject']);
1592             }
1593             
1594             if ($_isMime) {
1595                 $this->assertEquals(html_entity_decode('unittest vacation&nbsp;message', ENT_NOQUOTES, 'UTF-8'), $resultSet['reason']);
1596             } else {
1597                 $this->assertEquals($_sieveData['reason'], $resultSet['reason']);
1598             }
1599             
1600         } else if ((isset($_sieveData[0]['action_type']) || array_key_exists('action_type', $_sieveData[0]))) {
1601             $resultSet = $this->_json->saveRules($this->_account->getId(), $_sieveData);
1602             $this->assertEquals($_sieveData, $resultSet);
1603         }
1604         
1605         return $resultSet;
1606     }
1607 }