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