Merge branch '2014.11-develop' into 2015.07
[tine20] / tests / tine20 / Calendar / Frontend / iMIPTest.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Calendar
6  * @license     http://www.gnu.org/licenses/agpl.html
7  * @copyright   Copyright (c) 2011-2015 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * 
10  * @todo        add test testOrganizerSendBy
11  * @todo extend Calendar_TestCase
12  */
13
14 /**
15  * Test class for Calendar_Frontend_iMIP
16  */
17 class Calendar_Frontend_iMIPTest extends TestCase
18 {
19     /**
20      * event ids that should be deleted in tearDown
21      * 
22      * @var unknown_type
23      */
24     protected $_eventIdsToDelete = array();
25     
26     /**
27      * iMIP frontent to be tested
28      * 
29      * @var Calendar_Frontend_iMIP
30      */
31     protected $_iMIPFrontend = NULL;
32     
33     /**
34      * iMIP frontent to be tested
35      * 
36      * @var Calendar_Frontend_iMIPMock
37      */
38     protected $_iMIPFrontendMock = NULL;
39     
40     /**
41     * email test class
42     *
43     * @var Felamimail_Controller_MessageTest
44     */
45     protected $_emailTestClass;
46         
47     /**
48      * Runs the test methods of this class.
49      *
50      * @access public
51      * @static
52      */
53     public static function main()
54     {
55         $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Calendar iMIP Tests');
56         PHPUnit_TextUI_TestRunner::run($suite);
57     }
58
59     /**
60      * Sets up the fixture.
61      * This method is called before a test is executed.
62      *
63      * @access protected
64      */
65     protected function setUp()
66     {
67         Calendar_Controller_Event::getInstance()->sendNotifications(true);
68         
69         Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, false);
70         
71         $this->_iMIPFrontend = new Calendar_Frontend_iMIP();
72         $this->_iMIPFrontendMock = new Calendar_Frontend_iMIPMock();
73         
74         try {
75             $this->_emailTestClass = new Felamimail_Controller_MessageTest();
76             $this->_emailTestClass->setup();
77         } catch (Exception $e) {
78             // do nothing
79         }
80     }
81
82     /**
83      * Tears down the fixture
84      * This method is called after a test is executed.
85      *
86      * @access protected
87      */
88     protected function tearDown()
89     {
90         Calendar_Controller_Event::getInstance()->sendNotifications(false);
91         
92         if (! empty($this->_eventIdsToDelete)) {
93             $this->_deleteEvents(TRUE);
94         }
95         
96         if ($this->_emailTestClass instanceof Felamimail_Controller_MessageTest) {
97             $this->_emailTestClass->tearDown();
98         }
99     }
100     
101     /**
102      * testExternalInvitationRequestAutoProcess
103      */
104     public function testExternalInvitationRequestAutoProcess()
105     {
106         $ics = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/files/invitation_request_external.ics' );
107         $iMIP = new Calendar_Model_iMIP(array(
108             'id'             => Tinebase_Record_Abstract::generateUID(),
109             'ics'            => $ics,
110             'method'         => 'REQUEST',
111             'originator'     => 'l.kneschke@caldav.org',
112         ));
113         
114         $this->_iMIPFrontend->autoProcess($iMIP);
115         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
116         
117         $this->assertEmpty($prepared->existing_event, 'there should be no existing event');
118         $this->assertEmpty($prepared->preconditions, 'no preconditions should be raised');
119         $this->assertEquals(5, count($prepared->event->attendee));
120         $this->assertEquals('test mit extern', $prepared->event->summary);
121         
122         return $iMIP;
123     }
124
125     /**
126      * testSearchSharedCalendarsForExternalEvents
127      *
128      * @see 0011024: don't show external imip events in shared calendars
129      */
130     public function testSearchSharedCalendarsForExternalEvents()
131     {
132         $iMIP = $this->testExternalInvitationRequestAutoProcess();
133         $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_ACCEPTED);
134         $this->_eventIdsToDelete[] = $iMIP->event->getId();
135
136         $filter = new Calendar_Model_EventFilter(array(
137             array('field' => 'container_id', 'operator' => 'equals', 'value' => '/shared')
138         ));
139         $eventsInShared = Calendar_Controller_Event::getInstance()->search($filter);
140
141         $this->assertFalse(in_array($iMIP->event->getId(), $eventsInShared->getArrayOfIds()),
142             'found event in shared calendar: ' . print_r($iMIP->event->toArray(), true));
143     }
144
145     /**
146     * testSupportedPrecondition
147     */
148     public function testUnsupportedPrecondition()
149     {
150         $iMIP = $this->_getiMIP('PUBLISH');
151             
152         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
153     
154         $this->assertEquals(1, count($prepared->preconditions));
155         $this->assertEquals('processing published events is not supported yet', $prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_SUPPORTED][0]['message']);
156         $this->assertFalse($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_SUPPORTED][0]['check']);
157     }
158     
159     /**
160      * get iMIP record from internal event
161      * 
162      * @param string $_method
163      * @param boolean $_addEventToiMIP
164      * @return Calendar_Model_iMIP
165      */
166     protected function _getiMIP($_method, $_addEventToiMIP = FALSE, $_testEmptyMethod = FALSE)
167     {
168         $testConfig = Zend_Registry::get('testConfig');
169         $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
170         
171         $event = $this->_getEvent();
172         $event = Calendar_Controller_Event::getInstance()->create($event);
173         $this->_eventIdsToDelete[] = $event->getId();
174         
175         if ($_method == 'REPLY') {
176             $personas = Zend_Registry::get('personas');
177             $sclever = $personas['sclever'];
178             
179             $scleverAttendee = $event->attendee
180                 ->filter('status', Calendar_Model_Attender::STATUS_NEEDSACTION)
181                 ->getFirstRecord();
182             
183             $scleverAttendee->status = Calendar_Model_Attender::STATUS_ACCEPTED;
184             Calendar_Controller_Event::getInstance()->attenderStatusUpdate($event, $scleverAttendee, $scleverAttendee->status_authkey);
185             $event = Calendar_Controller_Event::getInstance()->get($event->getId());
186             $email = $sclever->accountEmailAddress;
187         }
188         
189         // get iMIP invitation for event
190         $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC);
191         $vevent = $converter->fromTine20Model($event);
192         $vevent->METHOD = $_method;
193         $ics = $vevent->serialize();
194         
195         $iMIP = new Calendar_Model_iMIP(array(
196             'id'             => Tinebase_Record_Abstract::generateUID(),
197             'ics'            => $ics,
198             'method'         => ($_testEmptyMethod) ? NULL : $_method,
199             'originator'     => $email,
200         ));
201         
202         if ($_addEventToiMIP) {
203             $iMIP->event = $event;
204         }
205         
206         return $iMIP;
207     }
208     
209     /**
210      * testInternalInvitationRequestAutoProcess
211      */
212     public function testInternalInvitationRequestAutoProcess()
213     {
214         $iMIP = $this->_getiMIP('REQUEST');
215         
216         $this->_iMIPFrontend->autoProcess($iMIP);
217         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
218         
219         $this->assertEquals(2, count($prepared->event->attendee), 'expected 2 attendee');
220         $this->assertEquals('Sleep very long', $prepared->event->summary);
221         $this->assertTrue(empty($prepared->preconditions));
222     }
223
224     /**
225     * testInternalInvitationRequestAutoProcessOwnStatusAlreadySet
226     */
227     public function testInternalInvitationRequestPreconditionOwnStatusAlreadySet()
228     {
229         $iMIP = $this->_getiMIP('REQUEST', TRUE);
230         
231         // set own status
232         $ownAttender = Calendar_Model_Attender::getOwnAttender($iMIP->getEvent()->attendee);
233         $ownAttender->status = Calendar_Model_Attender::STATUS_TENTATIVE;
234         Calendar_Controller_Event::getInstance()->attenderStatusUpdate($iMIP->getEvent(), $ownAttender, $ownAttender->status_authkey);
235         
236         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
237         $this->assertTrue(empty($prepared->preconditions), "it's ok to reanswer without reschedule!");
238         
239         // reschedule
240         $event = Calendar_Controller_Event::getInstance()->get($prepared->existing_event->getId());
241         $event->dtstart->addHour(2);
242         $event->dtend->addHour(2);
243         Calendar_Controller_Event::getInstance()->update($event, false);
244
245         $this->_iMIPFrontend->getExistingEvent($iMIP, true);
246         $iMIP->preconditionsChecked = false;
247         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
248         
249         $this->assertFalse(empty($prepared->preconditions), 'do not accept this iMIP after reshedule');
250         $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_RECENT]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_RECENT, $prepared->preconditions)));
251     }
252     
253     /**
254      * returns a simple event
255      *
256      * @return Calendar_Model_Event
257      * @param bool $_now
258      * @param bool $mute
259      * @todo replace with TestCase::_getEvent
260      */
261     protected function _getEvent($now = FALSE, $mute = NULL)
262     {
263         return new Calendar_Model_Event(array(
264             'summary'     => 'Sleep very long',
265             'dtstart'     => '2012-03-25 01:00:00',
266             'dtend'       => '2012-03-25 11:15:00',
267             'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ... not.',
268             'attendee'    => $this->_getAttendee(),
269             'organizer'   => Tinebase_Core::getUser()->contact_id,
270             'uid'         => Calendar_Model_Event::generateUID(),
271         ));
272     }
273     
274     /**
275      * get test attendee
276      *
277      * @return Tinebase_Record_RecordSet
278      */
279     protected function _getAttendee()
280     {
281         $personas = Zend_Registry::get('personas');
282         $sclever = $personas['sclever'];
283         
284         return new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
285             array(
286                 'user_id'        => Tinebase_Core::getUser()->contact_id,
287                 'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
288                 'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
289                 'status'         => Calendar_Model_Attender::STATUS_ACCEPTED,
290                 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
291             ),
292             array(
293                 'user_id'        => $sclever->contact_id,
294                 'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
295                 'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
296                 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
297             ),
298         ));
299     }
300     
301     /**
302      * delete events
303      *
304      * @return NULL
305      * @param boolean $purgeRecords true database delete or use is_deleted = 1
306      */
307     protected function _deleteEvents($purgeRecords = FALSE)
308     {
309         if ($purgeRecords) {
310             $be = new Calendar_Backend_Sql();
311             foreach ($this->_eventIdsToDelete as $idToDelete) {
312                 $be->delete($idToDelete);
313             }
314         } else {
315             Calendar_Controller_Event::getInstance()->delete($this->_eventIdsToDelete);
316         }
317     }
318
319     /**
320      * testExternalInvitationRequestProcess
321      */
322     public function testExternalInvitationRequestProcess()
323     {
324         $ics = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/files/invitation_request_external.ics' );
325         $ics = preg_replace('#DTSTART;VALUE=DATE-TIME;TZID=Europe/Berlin:20111121T130000#', 'DTSTART;VALUE=DATE-TIME;TZID=Europe/Berlin:' . Tinebase_DateTime::now()->addHour(1)->format('Ymd\THis'), $ics);
326         $ics = preg_replace('#DTEND;VALUE=DATE-TIME;TZID=Europe/Berlin:20111121T140000#', 'DTEND;VALUE=DATE-TIME;TZID=Europe/Berlin:' . Tinebase_DateTime::now()->addHour(2)->format('Ymd\THis'), $ics);
327         
328         $iMIP = new Calendar_Model_iMIP(array(
329                 'id'             => Tinebase_Record_Abstract::generateUID(),
330                 'ics'            => $ics,
331                 'method'         => 'REQUEST',
332                 'originator'     => 'l.kneschke@caldav.org',
333         ));
334         
335         Calendar_Controller_EventNotificationsTests::flushMailer();
336         $result = $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_ACCEPTED);
337         
338         $this->_iMIPFrontend->prepareComponent($iMIP);
339         $this->_eventIdsToDelete[] = $iMIP->event->getId();
340         
341         // assert external organizer
342         $this->assertEquals('l.kneschke@caldav.org', $iMIP->event->organizer->email, 'wrong organizer');
343         $this->assertTrue(empty($iMIP->event->organizer->account_id), 'organizer must not have an account');
344         
345         // assert attendee
346         $ownAttendee = Calendar_Model_Attender::getOwnAttender($iMIP->event->attendee);
347         $this->assertTrue(!! $ownAttendee, 'own attendee missing');
348         $this->assertEquals(5, count($iMIP->event->attendee), 'all attendee must be keeped');
349         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $ownAttendee->status, 'must be ACCEPTED');
350         
351         // assert REPLY message to organizer only
352         $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP);
353         if (isset($smtpConfig->from) && ! empty($smtpConfig->from)) {
354             $messages = Calendar_Controller_EventNotificationsTests::getMessages();
355             $this->assertEquals(1, count($messages), 'exactly one mail should be send');
356             $this->assertTrue(in_array('l.kneschke@caldav.org', $messages[0]->getRecipients()), 'organizer is not a receipient');
357             $this->assertContains('accepted', $messages[0]->getSubject(), 'wrong subject');
358             $this->assertContains('METHOD:REPLY', var_export($messages[0], TRUE), 'method missing');
359             $this->assertContains('SEQUENCE:0', var_export($messages[0], TRUE), 'external sequence has not been keepted');
360         }
361     }
362     
363     /**
364      * external organizer container should not be visible
365      */
366     public function testExternalContactContainer()
367     {
368         $this->testExternalInvitationRequestProcess();
369         $containerFrontend = new Tinebase_Frontend_Json_Container();
370         $result = $containerFrontend->getContainer('Calendar', Tinebase_Model_Container::TYPE_SHARED, null, null);
371         
372         foreach ($result as $container) {
373             if ($container['name'] === 'l.kneschke@caldav.org') {
374                 $this->fail('found external organizer container: ' . print_r($container, true));
375             }
376         }
377     }
378     
379     /**
380      * adds new imip message to Felamimail cache
381      * 
382      * @return Felamimail_Model_Message
383      */
384     protected function _addImipMessageToEmailCache()
385     {
386         $this->_checkIMAPConfig();
387         
388         // handle message with fmail (add to cache)
389         $message = $this->_emailTestClass->messageTestHelper('calendar_request.eml', NULL, NULL, array('unittest@tine20.org', $this->_getEmailAddress()));
390         return Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
391     }
392     
393     /**
394      * testDisabledExternalImip
395      */
396     public function testDisabledExternalImip()
397     {
398         Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, true);
399         $complete = $this->_addImipMessageToEmailCache();
400         $fmailJson = new Felamimail_Frontend_Json();
401         $jsonMessage = $fmailJson->getMessage($complete->getId());
402         Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, false);
403         $this->assertEmpty($jsonMessage['preparedParts']);
404     }
405     
406     /**
407      * check IMAP config and marks test as skipped if no IMAP backend is configured
408      */
409     protected function _checkIMAPConfig()
410     {
411         $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP);
412         if (! $imapConfig || ! isset($imapConfig->useSystemAccount)
413             || $imapConfig->useSystemAccount != TRUE
414             || ! $this->_emailTestClass instanceof Felamimail_Controller_MessageTest
415         ) {
416             $this->markTestSkipped('IMAP backend not configured');
417         }
418     }
419
420     /**
421      * testExternalPublishProcess
422      * - uses felamimail to cache external publish message
423      * 
424      * NOTE: meetup sends REQUEST w.o. attendee. We might think of autoconvert this to PUBLISH
425      */
426     public function testExternalPublishProcess()
427     {
428         $this->_checkIMAPConfig();
429         
430         // handle message with fmail (add to cache)
431         $message = $this->_emailTestClass->messageTestHelper('meetup.eml');
432         $complete = Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
433         
434         $iMIP = $complete->preparedParts->getFirstRecord()->preparedData;
435         
436         $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: ATTENDEE');
437         $result = $this->_iMIPFrontend->process($iMIP);
438     }
439
440     /**
441      * testInternalInvitationRequestProcess
442      */
443     public function testInternalInvitationRequestProcess()
444     {
445         $iMIP = $this->_getiMIP('REQUEST');
446         $result = $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_TENTATIVE);
447         
448         $event = $this->_iMIPFrontend->getExistingEvent($iMIP, true);
449         
450         $attender = Calendar_Model_Attender::getOwnAttender($event->attendee);
451         $this->assertEquals(Calendar_Model_Attender::STATUS_TENTATIVE, $attender->status);
452     }
453
454     /**
455      * testEmptyMethod
456      */
457     public function testEmptyMethod()
458     {
459         $iMIP = $this->_getiMIP('REQUEST', FALSE, TRUE);
460         
461         $this->assertEquals('REQUEST', $iMIP->method);
462     }
463     
464     /**
465      * testInternalInvitationReplyPreconditions
466      * 
467      * an internal reply does not need to be processed of course
468      */
469     public function testInternalInvitationReplyPreconditions()
470     {
471         $iMIP = $this->_getiMIP('REPLY');
472         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
473         
474         $this->assertFalse(empty($prepared->preconditions), 'empty preconditions');
475         $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_TOPROCESS]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_TOPROCESS, $prepared->preconditions)), 'missing PRECONDITION_TOPROCESS');
476     }
477     
478     /**
479      * testInternalInvitationReplyAutoProcess
480      * 
481      * an internal reply does not need to be processed of course
482      */
483     public function testInternalInvitationReplyAutoProcess()
484     {
485         // flush mailer
486         if (isset(Tinebase_Core::getConfig()->actionqueue)) {
487             Tinebase_ActionQueue::getInstance()->processQueue(10000);
488         }
489         Tinebase_Smtp::getDefaultTransport()->flush();
490         
491         $iMIP = $this->_getiMIP('REPLY', TRUE);
492         $event = $iMIP->getEvent();
493         
494         try {
495             $this->_iMIPFrontend->autoProcess($iMIP);
496         } catch (Exception $e) {
497             $this->assertContains('TOPROCESS', $e->getMessage());
498             return;
499         }
500         
501         $this->fail("autoProcess did not throw TOPROCESS Exception $e");
502     }
503     
504     /**
505      * testInvitationExternalReply
506      */
507     public function testInvitationExternalReply()
508     {
509         $testConfig = Zend_Registry::get('testConfig');
510         $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
511         
512         $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_reply_external_accepted.ics' );
513         $ics = preg_replace('/unittest@tine20\.org/', $email, $ics);
514         
515         $iMIP = new Calendar_Model_iMIP(array(
516             'id'             => Tinebase_Record_Abstract::generateUID(),
517             'ics'            => $ics,
518             'method'         => 'REPLY',
519             'originator'     => 'mail@corneliusweiss.de',
520         ));
521         
522         $this->assertEquals(1, $iMIP->getEvent()->seq);
523         $this->assertTrue(! empty($iMIP->getEvent()->last_modified_time));
524         
525         // force creation of external attendee
526         $externalAttendee = new Calendar_Model_Attender(array(
527             'user_type'     => Calendar_Model_Attender::USERTYPE_USER,
528             'user_id'       => $iMIP->getEvent()->attendee->getFirstRecord()->user_id,
529             'status'        => Calendar_Model_Attender::STATUS_NEEDSACTION
530         ));
531         
532         // create matching event
533         $event = new Calendar_Model_Event(array(
534             'summary'     => 'TEST7',
535             'dtstart'     => '2011-11-30 14:00:00',
536             'dtend'       => '2011-11-30 15:00:00',
537             'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ...',
538             'attendee'    => $this->_getAttendee(),
539             'organizer'   => Tinebase_Core::getUser()->contact_id,
540             'uid'         => 'a8d10369e051094ae9322bd65e8afecac010bfc8',
541         ));
542         $event->attendee->addRecord($externalAttendee);
543         $event = Calendar_Controller_Event::getInstance()->create($event);
544         $this->_eventIdsToDelete[] = $event->getId();
545         
546         // TEST NORMAL REPLY
547         try {
548             $this->_iMIPFrontend->autoProcess($iMIP);
549         } catch (Exception $e) {
550             $this->fail('TEST NORMAL REPLY autoProcess throws Exception: ' . $e);
551         }
552         unset($iMIP->existing_event);
553         
554         $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
555         $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
556         
557         $this->assertEquals(3, count($updatedEvent->attendee));
558         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
559         
560         // TEST ACCEPTABLE NON RECENT REPLY
561         $updatedExternalAttendee->status = Calendar_Model_Attender::STATUS_NEEDSACTION;
562         Calendar_Controller_Event::getInstance()->attenderStatusUpdate($updatedEvent, $updatedExternalAttendee, $updatedExternalAttendee->status_authkey);
563         try {
564             $iMIP->preconditionsChecked = false;
565             $this->_iMIPFrontend->autoProcess($iMIP);
566         } catch (Exception $e) {
567             $this->fail('TEST ACCEPTABLE NON RECENT REPLY autoProcess throws Exception: ' . $e);
568         }
569         unset($iMIP->existing_event);
570         
571         $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
572         $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
573         
574         $this->assertEquals(3, count($updatedEvent->attendee));
575         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
576         
577         // check if attendee are resolved
578         $existingEvent = $this->_iMIPFrontend->getExistingEvent($iMIP);
579         $this->assertTrue($iMIP->existing_event->attendee instanceof Tinebase_Record_RecordSet);
580         $this->assertEquals(3, count($iMIP->existing_event->attendee));
581         
582         // TEST NON ACCEPTABLE NON RECENT REPLY
583         $iMIP->preconditionsChecked = false;
584         try {
585             $this->_iMIPFrontend->autoProcess($iMIP);
586             $this->fail('autoProcess should throw Calendar_Exception_iMIP');
587         } catch (Calendar_Exception_iMIP $cei) {
588             $this->assertContains('iMIP preconditions failed: RECENT', $cei->getMessage());
589         }
590     }
591
592     /**
593      * testExternalInvitationCancelProcessEvent
594      *
595      */
596     public function testExternalInvitationCancelProcessEvent()
597     {
598         $iMIP = $this->testExternalInvitationRequestAutoProcess();
599         $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_ACCEPTED);
600         $this->_eventIdsToDelete[] = $iMIP->event->getId();
601
602         $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_cancel.ics' );
603
604         $iMIP = new Calendar_Model_iMIP(array(
605             'id'             => Tinebase_Record_Abstract::generateUID(),
606             'ics'            => $ics,
607             'method'         => 'CANCEL',
608             'originator'     => 'l.kneschke@caldav.org',
609         ));
610
611         // TEST CANCEL
612         try {
613             $this->_iMIPFrontend->autoProcess($iMIP);
614         } catch (Exception $e) {
615             $this->fail('TEST NORMAL CANCEL autoProcess throws Exception: ' . $e);
616         }
617         unset($iMIP->existing_event);
618
619         $existingEvent = $iMIP->getExistingEvent(true);
620         $this->assertNull($existingEvent, 'event must be deleted');
621
622         $existingEvent = $iMIP->getExistingEvent(true, true);
623         $this->assertEquals($existingEvent->is_deleted, 1, 'event must be deleted');
624     }
625
626     /**
627      * testExternalInvitationCancelProcessAttendee
628      *
629      */
630     public function testExternalInvitationCancelProcessAttendee()
631     {
632         $iMIP = $this->testExternalInvitationRequestAutoProcess();
633         $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_ACCEPTED);
634         $this->_eventIdsToDelete[] = $eventId = $iMIP->event->getId();
635
636         $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_cancel.ics' );
637         // set status to not cancelled, so that only attendees are removed from the event
638         $ics = preg_replace('#STATUS:CANCELLED#', 'STATUS:CONFIRMED', $ics);
639
640         $iMIP = new Calendar_Model_iMIP(array(
641             'id'             => Tinebase_Record_Abstract::generateUID(),
642             'ics'            => $ics,
643             'method'         => 'CANCEL',
644             'originator'     => 'l.kneschke@caldav.org',
645         ));
646
647         // TEST CANCEL
648         try {
649             $this->_iMIPFrontend->autoProcess($iMIP);
650         } catch (Exception $e) {
651             $this->fail('TEST NORMAL CANCEL autoProcess throws Exception: ' . $e);
652         }
653         unset($iMIP->existing_event);
654
655         $updatedEvent = Calendar_Controller_Event::getInstance()->get($eventId);
656         $this->assertEquals(3, count($updatedEvent->attendee), 'attendee count must be 3');
657     }
658
659     /**
660       * testInvitationCancel
661       * 
662       * @todo implement
663       */
664      public function testOrganizerSendBy()
665      {
666          $this->markTestIncomplete('implement me');
667      }
668 }