#9812: mark folder as read does not work with pgsql
[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-2014 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     public function testAddAndClearFlags()
674     {
675         $message = $this->_sendMessage();
676         $inboxBefore = $this->_getFolder('INBOX');
677         
678         $this->_json->addFlags($message['id'], Zend_Mail_Storage::FLAG_SEEN);
679         
680         // check if unread count got decreased
681         $inboxAfter = $this->_getFolder('INBOX');
682         $this->assertTrue($inboxBefore->cache_unreadcount - 1 == $inboxAfter->cache_unreadcount, 'wrong cache unreadcount');
683         
684         $message = $this->_json->getMessage($message['id']);
685         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_SEEN, $message['flags']), 'seen flag not set');
686         
687         // try with a filter
688         $filter = array(
689             array('field' => 'id', 'operator' => 'in', array($message['id']))
690         );
691         $this->_json->clearFlags($filter, Zend_Mail_Storage::FLAG_SEEN);
692         
693         $message = $this->_json->getMessage($message['id']);
694         $this->assertFalse(in_array(Zend_Mail_Storage::FLAG_SEEN, $message['flags']), 'seen flag should not be set');
695
696         $this->setExpectedException('Tinebase_Exception_NotFound');
697         $this->_json->addFlags(array($message['id']), Zend_Mail_Storage::FLAG_DELETED);
698         $this->_json->getMessage($message['id']);
699     }
700     
701     /**
702      * testMarkFolderRead
703      * 
704      * @see 0009812: mark folder as read does not work with pgsql
705      */
706     public function testMarkFolderRead()
707     {
708         $inboxBefore = $this->_getFolder('INBOX');
709         $filter = array(array(
710             'field' => 'folder_id', 'operator' => 'equals', 'value' => $inboxBefore->getId()
711         ), array(
712             'field' => 'flags', 'operator' => 'notin', 'value' => array(Zend_Mail_Storage::FLAG_SEEN)
713         ));
714         $this->_json->addFlags($filter, Zend_Mail_Storage::FLAG_SEEN);
715         
716         $inboxAfter = $this->_getFolder('INBOX');
717         $this->assertEquals(0, $inboxAfter->cache_unreadcount);
718     }
719     
720     /**
721      * test delete from trash
722      */
723     public function testDeleteFromTrashWithFilter()
724     {
725         $message = $this->_sendMessage();
726         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_account->trash_folder);
727         
728         $trash = $this->_getFolder($this->_account->trash_folder);
729         $result = $this->_json->moveMessages(array(array(
730             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
731         )), $trash->getId());
732
733         $messageInTrash = $this->_searchForMessageBySubject($message['subject'], $this->_account->trash_folder);
734         
735         // delete messages in trash with filter
736         $this->_json->addFlags(array(array(
737             'field' => 'folder_id', 'operator' => 'equals', 'value' => $trash->getId()
738         ), array(
739             'field' => 'id', 'operator' => 'in', 'value' => array($messageInTrash['id'])
740         )), Zend_Mail_Storage::FLAG_DELETED);
741         
742         $this->setExpectedException('Tinebase_Exception_NotFound');
743         $this->_json->getMessage($messageInTrash['id']);
744     }
745     
746     /**
747      * move message to trash with trash folder constant (Felamimail_Model_Folder::FOLDER_TRASH)
748      */
749     public function testMoveMessagesToTrash()
750     {
751         $message = $this->_sendMessage();
752         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_account->trash_folder);
753         
754         $result = $this->_json->moveMessages(array(array(
755             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
756         )), Felamimail_Model_Folder::FOLDER_TRASH);
757
758         $messageInTrash = $this->_searchForMessageBySubject($message['subject'], $this->_account->trash_folder);
759     }
760     
761     /**
762      * test reply mail and check some headers
763      * 
764      * @see 0006106: Add References header / https://forge.tine20.org/mantisbt/view.php?id=6106
765      */
766     public function testReplyMessage()
767     {
768         $message = $this->_sendMessage();
769         
770         $replyMessage = $this->_getReply($message);
771         $returned = $this->_json->saveMessage($replyMessage);
772         
773         $result = $this->_getMessages();
774         
775         $replyMessageFound = array();
776         $originalMessage = array();
777         foreach ($result['results'] as $mail) {
778             if ($mail['subject'] == $replyMessage['subject']) {
779                 $replyMessageFound = $mail;
780             }
781             if ($mail['subject'] == $message['subject']) {
782                 $originalMessage = $mail;
783             }
784         }
785         $replyMessageFound = $this->_json->getMessage($replyMessageFound['id']);
786         $originalMessage = $this->_json->getMessage($originalMessage['id']);
787         
788         $this->assertTrue(! empty($replyMessageFound), 'replied message not found');
789         $this->assertTrue(! empty($originalMessage), 'original message not found');
790         
791         // check headers
792         $this->assertTrue(isset($replyMessageFound['headers']['in-reply-to']));
793         $this->assertEquals($originalMessage['headers']['message-id'], $replyMessageFound['headers']['in-reply-to']);
794         $this->assertTrue(isset($replyMessageFound['headers']['references']));
795         $this->assertEquals($originalMessage['headers']['message-id'], $replyMessageFound['headers']['references']);
796         
797         // check answered flag
798         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_ANSWERED, $originalMessage['flags'], 'could not find flag'));
799     }
800     
801     /**
802      * get reply message data
803      * 
804      * @param array $_original
805      * @return array
806      */
807     protected function _getReply($_original)
808     {
809         $replyMessage               = $this->_getMessageData();
810         $replyMessage['subject']    = 'Re: ' . $_original['subject'];
811         $replyMessage['original_id']= $_original['id'];
812         $replyMessage['flags']      = Zend_Mail_Storage::FLAG_ANSWERED;
813         
814         return $replyMessage;
815     }
816
817     /**
818      * test reply mail in sent folder
819      */
820     public function testReplyMessageInSentFolder()
821     {
822         $messageInSent = $this->_sendMessage($this->_account->sent_folder);
823         $replyMessage = $this->_getReply($messageInSent);
824         $returned = $this->_json->saveMessage($replyMessage);
825         
826         $result = $this->_getMessages();
827         $sentMessage = $this->_getMessageFromSearchResult($result, $replyMessage['subject']);
828         $this->assertTrue(! empty($sentMessage));
829     }
830
831     /**
832      * test reply mail with long references header
833      * 
834      * @see 0006644: "At least one mail header line is too long"
835      */
836     public function testReplyMessageWithLongHeader()
837     {
838         $messageInSent = $this->_sendMessage($this->_account->sent_folder, array(
839             'references' => '<c95d8187-2c71-437e-adb8-5e1dcdbdc507@email.test.org>
840    <2601bbfa-566e-4490-a3db-aad005733d32@email.test.org>
841    <20120530154350.1854610131@ganymed.de>
842    <7e393ce1-d193-44fc-bf5f-30c61a271fe6@email.test.org>
843    <4FC8B49C.8040704@funk.de>
844    <dba2ad5c-6726-4171-8710-984847c010a1@email.test.org>
845    <20120601123551.5E98610131@ganymed.de>
846    <f1cc3195-8641-46e3-8f20-f60f3e16b107@email.test.org>
847    <20120619093658.37E4210131@ganymed.de>
848    <CA+6Rn2PX2Q3tOk2tCQfCjcaC8zYS5XZX327OoyJfUb+w87vCLQ@mail.net.com>
849    <20120619130652.03DD310131@ganymed.de>
850    <37616c6a-4c47-4b54-9ca6-56875bc9205d@email.test.org>
851    <20120620074843.42E2010131@ganymed.de>
852    <CA+6Rn2MAb2x0qeSfcaW6F=0S7LEQL442Sx2ha9RtwMs4B0esBg@mail.net.com>
853    <20120620092902.88C8C10131@ganymed.de>
854    <c95d8187-2c71-437e-adb8-5e1dcdbdc507@email.test.org>
855    <2601bbfa-566e-4490-a3db-aad005733d32@email.test.org>
856    <20120530154350.1854610131@ganymed.de>
857    <7e393ce1-d193-44fc-bf5f-30c61a271fe6@email.test.org>
858    <4FC8B49C.8040704@funk.de>
859    <dba2ad5c-6726-4171-8710-984847c010a1@email.test.org>
860    <20120601123551.5E98610131@ganymed.de>
861    <f1cc3195-8641-46e3-8f20-f60f3e16b107@email.test.org>
862    <20120619093658.37E4210131@ganymed.de>
863    <CA+6Rn2PX2Q3tOk2tCQfCjcaC8zYS5XZX327OoyJfUb+w87vCLQ@mail.net.com>
864    <20120619130652.03DD310131@ganymed.de>
865    <37616c6a-4c47-4b54-9ca6-56875bc9205d@email.test.org>
866    <20120620074843.42E2010131@ganymed.de>
867    <CA+6Rn2MAb2x0qeSfcaW6F=0S7LEQL442Sx2ha9RtwMs4B0esBg@mail.net.com>
868    <20120620092902.88C8C10131@ganymed.de>'
869         ));
870         $replyMessage = $this->_getReply($messageInSent);
871         $returned = $this->_json->saveMessage($replyMessage);
872         
873         $result = $this->_getMessages();
874         $sentMessage = $this->_getMessageFromSearchResult($result, $replyMessage['subject']);
875         $this->assertTrue(! empty($sentMessage));
876     }
877     
878     /**
879      * test move
880      */
881     public function testMoveMessage()
882     {
883         $message = $this->_sendMessage();
884         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_testFolderName);
885         
886         $inbox = $this->_getFolder('INBOX');
887         $inboxBefore = $this->_json->updateMessageCache($inbox['id'], 30);
888         
889         // move
890         $testFolder = $this->_getFolder($this->_testFolderName);
891         $result = $this->_json->moveMessages(array(array(
892             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
893         )), $testFolder->getId());
894
895         $inboxAfter = $this->_getFolder('INBOX');
896         
897         // check if count was decreased correctly
898         $this->assertEquals($inboxBefore['cache_unreadcount'] - 1, $inboxAfter['cache_unreadcount']);
899         $this->assertEquals($inboxBefore['cache_totalcount'] - 1, $inboxAfter['cache_totalcount']);
900         
901         $result = $this->_getMessages($this->_testFolderName);
902         $movedMessage = array();
903         foreach ($result['results'] as $mail) {
904             if ($mail['subject'] == $message['subject']) {
905                 $movedMessage = $mail;
906             }
907         }
908         $this->assertTrue(! empty($movedMessage), 'moved message not found');
909     }
910     
911     /**
912      * forward message test
913      * 
914      * @see 0007624: losing umlauts in attached filenames
915      */
916     public function testForwardMessageWithAttachment()
917     {
918         $testFolder = $this->_getFolder($this->_testFolderName);
919         $message = fopen(dirname(__FILE__) . '/files/multipart_related.eml', 'r');
920         Felamimail_Controller_Message::getInstance()->appendMessage($testFolder, $message);
921         
922         $subject = 'Tine 2.0 bei Metaways - Verbessurngsvorschlag';
923         $message = $this->_searchForMessageBySubject($subject, $this->_testFolderName);
924         
925         $fwdSubject = 'Fwd: ' . $subject;
926         $forwardMessageData = array(
927             'account_id'    => $this->_account->getId(),
928             'subject'       => $fwdSubject,
929             'to'            => array('unittest@' . $this->_mailDomain),
930             'body'          => "aaaaaä <br>",
931             'headers'       => array('X-Tine20TestMessage' => 'jsontest'),
932             'original_id'   => $message['id'],
933             'attachments'   => array(new Tinebase_Model_TempFile(array(
934                 'type'  => Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822,
935                 'name'  => 'Verbessurüngsvorschlag',
936             ), TRUE)),
937             'flags'         => Zend_Mail_Storage::FLAG_PASSED,
938         );
939         
940         $this->_foldersToClear[] = 'INBOX';
941         $this->_json->saveMessage($forwardMessageData);
942         $forwardMessage = $this->_searchForMessageBySubject($fwdSubject);
943         
944         // check attachment name
945         $forwardMessageComplete = $this->_json->getMessage($forwardMessage['id']);
946         $this->assertEquals(1, count($forwardMessageComplete['attachments']));
947         $this->assertEquals('Verbessurüngsvorschlag.eml', $forwardMessageComplete['attachments'][0]['filename'], 'umlaut missing from attachment filename');
948         
949         $forwardMessage = $this->_json->getMessage($forwardMessage['id']);
950         $this->assertTrue((isset($forwardMessage['structure']) || array_key_exists('structure', $forwardMessage)), 'structure should be set when fetching complete message: ' . print_r($forwardMessage, TRUE));
951         $this->assertEquals(Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822, $forwardMessage['structure']['parts'][2]['contentType']);
952         
953         $message = $this->_json->getMessage($message['id']);
954         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_PASSED, $message['flags']), 'forwarded flag missing in flags: ' . print_r($message, TRUE));
955     }
956     
957     /**
958      * testSendMessageWithAttachmentWithoutExtension
959      * 
960      * @see 0008328: email attachment without file extension is not sent properly
961      */
962     public function testSendMessageWithAttachmentWithoutExtension()
963     {
964         $subject = 'attachment test';
965         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain, $subject);
966         $tempfileName = 'jsontest' . Tinebase_Record_Abstract::generateUID(10);
967         $tempfilePath = Tinebase_Core::getTempDir() . DIRECTORY_SEPARATOR . $tempfileName;
968         file_put_contents($tempfilePath, 'some content');
969         $tempFile = Tinebase_TempFile::getInstance()->createTempFile($tempfilePath, $tempfileName);
970         $messageToSend['attachments'] = array(array('tempFile' => array('id' => $tempFile->getId())));
971         $this->_json->saveMessage($messageToSend);
972         $forwardMessage = $this->_searchForMessageBySubject($subject);
973         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
974         
975         $fullMessage = $this->_json->getMessage($forwardMessage['id']);
976         $this->assertTrue(count($fullMessage['attachments']) === 1);
977         $attachment = $fullMessage['attachments'][0];
978         $this->assertContains($tempfileName, $attachment['filename'], 'wrong attachment filename: ' . print_r($attachment, TRUE));
979         $this->assertEquals(16, $attachment['size'], 'wrong attachment size: ' . print_r($attachment, TRUE));
980     }
981     
982     /**
983      * save message in folder (draft) test
984      * 
985      * @see 0007178: BCC does not save the draft message
986      */
987     public function testSaveMessageInFolder()
988     {
989         $messageToSave = $this->_getMessageData();
990         $messageToSave['bcc'] = array('bccaddress@email.org', 'bccaddress2@email.org');
991         
992         $draftsFolder = $this->_getFolder($this->_account->drafts_folder);
993         $returned = $this->_json->saveMessageInFolder($this->_account->drafts_folder, $messageToSave);
994         $this->_foldersToClear = array($this->_account->drafts_folder);
995         
996         // check if message is in drafts folder and recipients are present
997         $message = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_account->drafts_folder);
998         $this->assertEquals($messageToSave['subject'],  $message['subject']);
999         $this->assertEquals($messageToSave['to'][0],    $message['to'][0], 'recipient not found');
1000         $this->assertEquals(2, count($message['bcc']), 'bcc recipient not found: ' . print_r($message, TRUE));
1001         $this->assertEquals($messageToSave['bcc'][0],   $message['bcc'][0], '1st bcc recipient not found');
1002         $this->assertEquals($messageToSave['bcc'][1],   $message['bcc'][1], '2nd bcc recipient not found');
1003     }
1004     
1005     /**
1006      * testSendReadingConfirmation
1007      * 
1008      * @see 0007736: ask user before sending reading confirmation
1009      * @see 0008402: Wrong recipient with read confirmation
1010      */
1011     public function testSendReadingConfirmation()
1012     {
1013         $messageToSave = $this->_getMessageData();
1014         $messageToSave['headers']['disposition-notification-to'] = '"' . Tinebase_Core::getUser()->accountFullName . '" <' . $this->_account->email . '>';
1015         $returned = $this->_json->saveMessageInFolder($this->_testFolderName, $messageToSave);
1016         $messageWithReadingConfirmationHeader = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_testFolderName);
1017         $this->_messageIds[] = $messageWithReadingConfirmationHeader['id'];
1018         $this->_json->sendReadingConfirmation($messageWithReadingConfirmationHeader['id']);
1019         
1020         $translate = Tinebase_Translation::getTranslation('Felamimail');
1021         $subject = $translate->_('Reading Confirmation:') . ' '. $messageToSave['subject'];
1022         $message = $this->_searchForMessageBySubject($subject);
1023         $this->_messageIds[] = $message['id'];
1024         
1025         $complete = $this->_json->getMessage($message['id']);
1026         $this->assertContains($translate->_('Was read by:') . ' ' . $this->_account->from, $complete['body']);
1027     }
1028
1029     /**
1030      * save message in non-existant folder (templates) test
1031      * 
1032      * @see 0008476: Drafts are not working
1033      */
1034     public function testSaveMessageInNonExistantTemplatesFolder()
1035     {
1036         $messageToSave = $this->_getMessageData();
1037         
1038         $templatesFolder = $this->_getFolder($this->_account->templates_folder, FALSE);
1039         if ($templatesFolder) {
1040             $this->_json->deleteFolder($templatesFolder['id'], $this->_account->getId());
1041         }
1042         $returned = $this->_json->saveMessageInFolder($this->_account->templates_folder, $messageToSave);
1043         $this->_foldersToClear = array($this->_account->templates_folder);
1044         
1045         // check if message is in templates folder
1046         $message = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_account->templates_folder);
1047         $this->assertEquals($messageToSave['subject'],  $message['subject']);
1048         $this->assertEquals($messageToSave['to'][0],    $message['to'][0], 'recipient not found');
1049     }
1050     
1051     /**
1052      * testSaveMessageNoteWithInvalidChar
1053      * 
1054      * @see 0008644: error when sending mail with note (wrong charset)
1055      */
1056     public function testSaveMessageNoteWithInvalidChar()
1057     {
1058         $subject = Tinebase_Core::filterInputForDatabase("\xF0\x9F\x98\x8A\xC2"); // :-) emoji &nbsp;
1059         $messageData = $this->_getMessageData('', $subject);
1060         $messageData['note'] = true;
1061         $messageData['body'] .= "&nbsp;";
1062         
1063         $this->_foldersToClear[] = 'INBOX';
1064         $this->_json->saveMessage($messageData);
1065         $message = $this->_searchForMessageBySubject($subject);
1066         
1067         $contact = Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
1068         $this->_checkEmailNote($contact, $subject);
1069     }
1070     
1071     /**
1072      * testSaveMessageNoteWithInvalidChar
1073      * 
1074      * @see 0008644: error when sending mail with note (wrong charset)
1075      */
1076     public function testSaveMessageWithInvalidChar()
1077     {
1078         $subject = "\xF0\x9F\x98\x8A"; // :-) emoji
1079         $messageData = $this->_getMessageData('', $subject);
1080         $this->_foldersToClear[] = 'INBOX';
1081         $this->_json->saveMessage($messageData);
1082         $message = $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
1083     }
1084     
1085     /**
1086      * testMessageWithInvalidICS
1087      * 
1088      * @see 0008786: broken ics causes js error when showing details
1089      */
1090     public function testMessageWithInvalidICS()
1091     {
1092         $inbox = $this->_getFolder('INBOX');
1093         $mailAsString = file_get_contents(dirname(__FILE__) . '/files/invalidimip.eml');
1094         Felamimail_Controller_Message::getInstance()->appendMessage($inbox, $mailAsString);
1095         
1096         $this->_foldersToClear = array('INBOX');
1097         $message = $this->_searchForMessageBySubject('test invalid imip');
1098         
1099         $fullMessage = $this->_json->getMessage($message['id']);
1100         $this->assertTrue(empty($fullMessage->preparedParts));
1101     }
1102     
1103     /*********************** sieve tests ****************************/
1104     
1105     /**
1106      * set and get vacation sieve script
1107      * 
1108      * @see 0007768: Sieve - Vacation notify frequency not being set (Cyrus)
1109      */
1110     public function testGetSetVacation()
1111     {
1112         $vacationData = $this->_getVacationData();
1113         $this->_sieveTestHelper($vacationData);
1114         
1115         // check if script was activated
1116         $activeScriptName = Felamimail_Controller_Sieve::getInstance()->getActiveScriptName($this->_account->getId());
1117         $this->assertEquals($this->_testSieveScriptName, $activeScriptName);
1118         $updatedAccount = Felamimail_Controller_Account::getInstance()->get($this->_account->getId());
1119         $this->assertTrue((bool) $updatedAccount->sieve_vacation_active);
1120         
1121         $result = $this->_json->getVacation($this->_account->getId());
1122
1123         $this->assertEquals($this->_account->email, $result['addresses'][0]);
1124         
1125         $sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1126         if (preg_match('/dbmail/i', $sieveBackend->getImplementation())) {
1127             $translate = Tinebase_Translation::getTranslation('Felamimail');
1128             $vacationData['subject'] = sprintf($translate->_('Out of Office reply from %1$s'), Tinebase_Core::getUser()->accountFullName);
1129         }
1130         
1131         foreach (array('reason', 'enabled', 'subject', 'from', 'days') as $field) {
1132             $this->assertEquals($vacationData[$field], $result[$field], 'vacation data mismatch: ' . $field);
1133         }
1134     }
1135     
1136     /**
1137      * get vacation data
1138      * 
1139      * @return array
1140      */
1141     protected function _getVacationData()
1142     {
1143         return array(
1144             'id'                    => $this->_account->getId(),
1145             'subject'               => 'unittest vacation subject',
1146             'from'                  => $this->_account->from . ' <' . $this->_account->email . '>',
1147             'days'                  => 3,
1148             'enabled'               => TRUE,
1149             'reason'                => 'unittest vacation message<br /><br />signature',
1150             'mime'                  => NULL,
1151         );
1152     }
1153     
1154     /**
1155      * test mime vacation sieve script
1156      */
1157     public function testMimeVacation()
1158     {
1159         $vacationData = $this->_getVacationData();
1160         $vacationData['reason'] = "\n<html><body><h1>unittest vacation&nbsp;message</h1></body></html>";
1161         
1162         $_sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1163         if (! in_array('mime', $_sieveBackend->capability())) {
1164             $vacationData['mime'] = 'text/html';
1165         }
1166         
1167         $this->_sieveTestHelper($vacationData, TRUE);
1168     }
1169     
1170     /**
1171      * test get/set of rules sieve script
1172      */
1173     public function testGetSetRules()
1174     {
1175         $ruleData = $this->_getRuleData();
1176         
1177         $this->_sieveTestHelper($ruleData);
1178         
1179         // check getRules
1180         $result = $this->_json->getRules($this->_account->getId());
1181         $this->assertEquals($result['totalcount'], count($ruleData));
1182         
1183         // check by sending mail
1184         $messageData = $this->_getMessageData('', 'viagra');
1185         $returned = $this->_json->saveMessage($messageData);
1186         $this->_foldersToClear = array('INBOX', $this->_testFolderName);
1187         // check if message is in test folder
1188         $message = $this->_searchForMessageBySubject($messageData['subject'], $this->_testFolderName);
1189     }
1190     
1191     /**
1192      * testRemoveRules
1193      * 
1194      * @see 0006490: can not delete single filter rule
1195      */
1196     public function testRemoveRules()
1197     {
1198         $this->testGetSetRules();
1199         $this->_json->saveRules($this->_account->getId(), array());
1200         
1201         $result = $this->_json->getRules($this->_account->getId());
1202         $this->assertEquals(0, $result['totalcount'], 'found rules: ' . print_r($result, TRUE));
1203     }
1204     
1205     /**
1206      * get sieve rule data
1207      * 
1208      * @return array
1209      */
1210     protected function _getRuleData()
1211     {
1212         return array(array(
1213             'id'            => 1,
1214             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO, 
1215             'action_argument' => $this->_testFolderName,
1216             'conjunction'  => 'allof',
1217             'conditions'    => array(array(
1218                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1219                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1220                 'header'        => 'From',
1221                 'key'           => '"abcd" <info@example.org>',
1222             )),
1223             'enabled'       => 1,
1224         ), array(
1225             'id'            => 2,
1226             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO, 
1227             'action_argument' => $this->_testFolderName,
1228             'conjunction'  => 'allof',
1229             'conditions'    => array(array(
1230                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1231                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1232                 'header'        => 'From',
1233                 'key'           => 'info@example.org',
1234             )),
1235             'enabled'       => 0,
1236         ), array(
1237             'id'            => 3,
1238             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO, 
1239             'action_argument' => $this->_testFolderName,
1240             'conjunction'  => 'allof',
1241             'conditions'    => array(array(
1242                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_HEADER,
1243                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_REGEX,
1244                 'header'        => 'subject',
1245                 'key'           => '[vV]iagra|cyalis',
1246             )),
1247             'enabled'       => 1,
1248         ));
1249     }
1250     
1251     /**
1252      * test to set a forward rule to this accounts email address
1253      * -> should throw exception to prevent mail cycling
1254      */
1255     public function testSetForwardRuleToSelf()
1256     {
1257         $ruleData = array(array(
1258             'id'            => 1,
1259             'action_type'   => Felamimail_Sieve_Rule_Action::REDIRECT, 
1260             'action_argument' => $this->_account->email,
1261             'conjunction'     => 'allof',
1262             'conditions'    => array(array(
1263                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1264                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1265                 'header'        => 'From',
1266                 'key'           => 'info@example.org',
1267             )),
1268             'enabled'       => 1,
1269         ));
1270         
1271         try {
1272             $this->_sieveTestHelper($ruleData);
1273             $this->assertTrue(FALSE, 'it is not allowed to set own email address for redirect!');
1274         } catch (Felamimail_Exception_Sieve $e) {
1275             $this->assertTrue(TRUE);
1276         }
1277
1278         // this should work
1279         $ruleData[0]['enabled'] = 0;
1280         $this->_sieveTestHelper($ruleData);
1281     }
1282     
1283     /**
1284      * testGetVacationTemplates
1285      * 
1286      * @return array
1287      */
1288     public function testGetVacationTemplates()
1289     {
1290         $this->_addVacationTemplateFile();
1291         $result = $this->_json->getVacationMessageTemplates();
1292         
1293         $this->assertTrue($result['totalcount'] > 0, 'no templates found');
1294         $found = FALSE;
1295         foreach ($result['results'] as $template) {
1296             if ($template['name'] === $this->_sieveVacationTemplateFile) {
1297                 $found = TRUE;
1298                 break;
1299             }
1300         }
1301         
1302         $this->assertTrue($found, 'wrong templates: ' . print_r($result['results'], TRUE));
1303         
1304         return $template;
1305     }
1306     
1307     /**
1308      * add vacation template file to vfs
1309      */
1310     protected function _addVacationTemplateFile()
1311     {
1312         $webdavRoot = new DAV\ObjectTree(new Tinebase_WebDav_Root());
1313         $path = '/webdav/Felamimail/shared/Vacation Templates';
1314         $node = $webdavRoot->getNodeForPath($path);
1315         $this->_pathsToDelete[] = $path . '/' . $this->_sieveVacationTemplateFile;
1316         $node->createFile($this->_sieveVacationTemplateFile, fopen(dirname(__FILE__) . '/files/' . $this->_sieveVacationTemplateFile, 'r'));
1317     }
1318     
1319     /**
1320      * testGetVacationMessage
1321      */
1322     public function testGetVacationMessage()
1323     {
1324         $result = $this->_getVacationMessageWithTemplate();
1325         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1326         $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 (" .
1327             $sclever->accountEmailAddress . ").<br /><br />I am on vacation until Apr 20, 2012. Please contact Paul Wulf<br />(pwulf@tine20.org) or Susan Clever (" .
1328             $sclever->accountEmailAddress . ") instead.<br /><br />" .
1329             Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId())->n_fn, $result['message']);
1330     }
1331     
1332     /**
1333      * get vacation message with template
1334      * 
1335      * @return array
1336      */
1337     protected function _getVacationMessageWithTemplate()
1338     {
1339         $template = $this->testGetVacationTemplates();
1340         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1341         $result = $this->_json->getVacationMessage(array(
1342             'start_date' => '2012-04-18',
1343             'end_date'   => '2012-04-20',
1344             'contact_ids' => array(
1345                 Tinebase_User::getInstance()->getFullUserByLoginName('pwulf')->contact_id,
1346                 $sclever->contact_id,
1347             ),
1348             'template_id' => $template['id'],
1349             'signature' => $this->_account->signature
1350         ));
1351         
1352         return $result;
1353     }
1354     
1355     /**
1356      * testGetVacationWithSignature
1357      * 
1358      * @see 0006866: check signature linebreaks in vacation message from template
1359      */
1360     public function testGetVacationWithSignature()
1361     {
1362         $this->_sieveVacationTemplateFile = 'vacation_template_sig.tpl';
1363         
1364         // set signature with <br> + linebreaks
1365         $this->_account->signature = "llalala<br>\nxyz<br>\nblubb<br>";
1366         
1367         $result = $this->_getVacationMessageWithTemplate();
1368         $this->assertContains('-- <br />llalala<br />xyz<br />blubb<br />', $result['message'], 'wrong linebreaks or missing signature');
1369     }
1370     
1371     /**
1372     * testSetVacationWithStartAndEndDate
1373     *
1374     * @see 0006266: automatic deactivation of vacation message
1375     */
1376     public function testSetVacationWithStartAndEndDate()
1377     {
1378         $vacationData = $this->_getVacationData();
1379         $vacationData['start_date'] = '2012-04-18';
1380         $vacationData['end_date'] = '2012-04-20';
1381         $result = $this->_sieveTestHelper($vacationData);
1382         
1383         $this->assertContains($vacationData['start_date'], $result['start_date']);
1384         $this->assertContains($vacationData['end_date'], $result['end_date']);
1385     }
1386     
1387     /**
1388      * testSieveRulesOrder
1389      * 
1390      * @see 0007240: order of sieve rules changes when vacation message is saved
1391      */
1392     public function testSieveRulesOrder()
1393     {
1394         $this->_setTestScriptname();
1395         
1396         // disable vacation first
1397         $this->_setDisabledVacation();
1398         
1399         $sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1400         
1401         $ruleData = $this->_getRuleData();
1402         $ruleData[0]['id'] = $ruleData[2]['id'];
1403         $ruleData[2]['id'] = 11;
1404         $resultSet = $this->_json->saveRules($this->_account->getId(), $ruleData);
1405         $sieveScriptRules = $sieveBackend->getScript($this->_testSieveScriptName);
1406         
1407         $this->_setDisabledVacation();
1408         $sieveScriptVacation = $sieveBackend->getScript($this->_testSieveScriptName);
1409         
1410         // compare sieve scripts
1411         $this->assertContains($sieveScriptRules, $sieveScriptVacation, 'rule order changed');
1412     }
1413     
1414     /**
1415      * use another name for test sieve script
1416      */
1417     protected function _setTestScriptname()
1418     {
1419         $this->_oldActiveSieveScriptName = Felamimail_Controller_Sieve::getInstance()->getActiveScriptName($this->_account->getId());
1420         $this->_testSieveScriptName = 'Felamimail_Unittest';
1421         Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_testSieveScriptName);
1422     }
1423     
1424     /**
1425      * set disabled vacation message
1426      */
1427     protected function _setDisabledVacation()
1428     {
1429         $vacationData = $this->_getVacationData();
1430         $vacationData['enabled'] = FALSE;
1431         $resultSet = $this->_json->saveVacation($vacationData);
1432     }
1433     
1434     /**
1435      * get folder filter
1436      *
1437      * @return array
1438      */
1439     protected function _getFolderFilter()
1440     {
1441         return array(array(
1442             'field' => 'globalname', 'operator' => 'equals', 'value' => ''
1443         ));
1444     }
1445
1446     /**
1447      * get message filter
1448      *
1449      * @param string $_folderId
1450      * @return array
1451      */
1452     protected function _getMessageFilter($_folderId)
1453     {
1454         $result = array(array(
1455             'field' => 'folder_id', 'operator' => 'equals', 'value' => $_folderId
1456         ));
1457         
1458         return $result;
1459     }
1460     
1461     /**
1462      * get mailbox
1463      *
1464      * @param string $name
1465      * @param boolean $createFolder
1466      * @return Felamimail_Model_Folder|NULL
1467      */
1468     protected function _getFolder($name, $createFolder = TRUE)
1469     {
1470         Felamimail_Controller_Cache_Folder::getInstance()->update($this->_account->getId());
1471         try {
1472             $folder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($this->_account->getId(), $name);
1473         } catch (Tinebase_Exception_NotFound $tenf) {
1474             $folder = ($createFolder) ? Felamimail_Controller_Folder::getInstance()->create($this->_account, $name) : NULL;
1475         }
1476         
1477         return $folder;
1478     }
1479
1480     /**
1481      * get message data
1482      *
1483      * @return array
1484      */
1485     protected function _getMessageData($_emailFrom = '', $_subject = 'test')
1486     {
1487         return array(
1488             'account_id'    => $this->_account->getId(),
1489             'subject'       => $_subject,
1490             'to'            => array(Tinebase_Core::getUser()->accountEmailAddress),
1491             'body'          => 'aaaaaä <br>',
1492             'headers'       => array('X-Tine20TestMessage' => 'jsontest'),
1493             'from_email'    => $_emailFrom,
1494             'content_type'  => Felamimail_Model_Message::CONTENT_TYPE_HTML,
1495         );
1496     }
1497
1498     /**
1499      * send message and return message array
1500      *
1501      * @param string $folderName
1502      * @param array $addtionalHeaders
1503      * @return array
1504      */
1505     protected function _sendMessage($folderName = 'INBOX', $addtionalHeaders = array())
1506     {
1507         $messageToSend = $this->_getMessageData();
1508         $messageToSend['headers'] = array_merge($messageToSend['headers'], $addtionalHeaders);
1509         $returned = $this->_json->saveMessage($messageToSend);
1510         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
1511         
1512         $result = $this->_getMessages($folderName);
1513         $message = $this->_getMessageFromSearchResult($result, $messageToSend['subject']);
1514         
1515         $this->assertTrue(! empty($message), 'Sent message not found.');
1516         
1517         return $message;
1518     }
1519     
1520     /**
1521      * returns message array from result
1522      * 
1523      * @param array $_result
1524      * @param string $_subject
1525      * @return array
1526      */
1527     protected function _getMessageFromSearchResult($_result, $_subject)
1528     {
1529         $message = array();
1530         foreach ($_result['results'] as $mail) {
1531             if ($mail['subject'] == $_subject) {
1532                 $message = $mail;
1533             }
1534         }
1535         
1536         return $message;
1537     }
1538     
1539     /**
1540      * get messages from folder
1541      * 
1542      * @param string $_folderName
1543      * @return array
1544      */
1545     protected function _getMessages($_folderName = 'INBOX')
1546     {
1547         $folder = $this->_getFolder($_folderName);
1548         $filter = $this->_getMessageFilter($folder->getId());
1549         // update cache
1550         $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10, 1);
1551         $i = 0;
1552         while ($folder->cache_status != Felamimail_Model_Folder::CACHE_STATUS_COMPLETE && $i < 10) {
1553             $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10);
1554             $i++;
1555         }
1556         $result = $this->_json->searchMessages($filter, '');
1557         //print_r($result);
1558         
1559         return $result;
1560     }
1561     
1562     /**
1563      * search for message defined by subject in folder
1564      * 
1565      * @param string $_subject
1566      * @param string $_folderName
1567      * @return string message data
1568      */
1569     protected function _searchForMessageBySubject($_subject, $_folderName = 'INBOX')
1570     {
1571         $result = $this->_getMessages($_folderName);
1572         
1573         $message = array();
1574         foreach ($result['results'] as $mail) {
1575             if ($mail['subject'] == $_subject) {
1576                 $message = $mail;
1577             }
1578         }
1579         $this->assertGreaterThan(0, $result['totalcount'], 'folder is empty');
1580         $this->assertTrue(! empty($message), 'Message not found');
1581         
1582         return $message;
1583     }
1584     
1585     /**
1586      * sieve test helper
1587      * 
1588      * @param array $_sieveData
1589      * @return array
1590      */
1591     protected function _sieveTestHelper($_sieveData, $_isMime = FALSE)
1592     {
1593         $this->_setTestScriptname();
1594         
1595         // check which save fn to use
1596         if ((isset($_sieveData['reason']) || array_key_exists('reason', $_sieveData))) {
1597             $resultSet = $this->_json->saveVacation($_sieveData);
1598             $this->assertEquals($this->_account->email, $resultSet['addresses'][0]);
1599             
1600             $_sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1601             
1602             if (preg_match('/dbmail/i', $_sieveBackend->getImplementation())) {
1603                 $translate = Tinebase_Translation::getTranslation('Felamimail');
1604                 $this->assertEquals(sprintf(
1605                     $translate->_('Out of Office reply from %1$s'), Tinebase_Core::getUser()->accountFullName), 
1606                     $resultSet['subject']
1607                 );
1608             } else {
1609                 $this->assertEquals($_sieveData['subject'], $resultSet['subject']);
1610             }
1611             
1612             if ($_isMime) {
1613                 $this->assertEquals(html_entity_decode('unittest vacation&nbsp;message', ENT_NOQUOTES, 'UTF-8'), $resultSet['reason']);
1614             } else {
1615                 $this->assertEquals($_sieveData['reason'], $resultSet['reason']);
1616             }
1617             
1618         } else if ((isset($_sieveData[0]['action_type']) || array_key_exists('action_type', $_sieveData[0]))) {
1619             $resultSet = $this->_json->saveRules($this->_account->getId(), $_sieveData);
1620             $this->assertEquals($_sieveData, $resultSet);
1621         }
1622         
1623         return $resultSet;
1624     }
1625 }