0010046: config for disabling external imip
[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-2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  */
10
11 /**
12  * Test class for Calendar_Frontend_iMIP
13  */
14 class Calendar_Frontend_iMIPTest extends TestCase
15 {
16     /**
17      * event ids that should be deleted in tearDown
18      * 
19      * @var unknown_type
20      */
21     protected $_eventIdsToDelete = array();
22     
23     /**
24      * iMIP frontent to be tested
25      * 
26      * @var Calendar_Frontend_iMIP
27      */
28     protected $_iMIPFrontend = NULL;
29     
30     /**
31      * iMIP frontent to be tested
32      * 
33      * @var Calendar_Frontend_iMIPMock
34      */
35     protected $_iMIPFrontendMock = NULL;
36     
37     /**
38     * email test class
39     *
40     * @var Felamimail_Controller_MessageTest
41     */
42     protected $_emailTestClass;
43         
44     /**
45      * Runs the test methods of this class.
46      *
47      * @access public
48      * @static
49      */
50     public static function main()
51     {
52         $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Calendar iMIP Tests');
53         PHPUnit_TextUI_TestRunner::run($suite);
54     }
55
56     /**
57      * Sets up the fixture.
58      * This method is called before a test is executed.
59      *
60      * @access protected
61      */
62     protected function setUp()
63     {
64         Calendar_Controller_Event::getInstance()->sendNotifications(true);
65         
66         $this->_iMIPFrontend = new Calendar_Frontend_iMIP();
67         $this->_iMIPFrontendMock = new Calendar_Frontend_iMIPMock();
68         
69         try {
70             $this->_emailTestClass = new Felamimail_Controller_MessageTest();
71             $this->_emailTestClass->setup();
72         } catch (Exception $e) {
73             // do nothing
74         }
75     }
76
77     /**
78      * Tears down the fixture
79      * This method is called after a test is executed.
80      *
81      * @access protected
82      */
83     protected function tearDown()
84     {
85         Calendar_Controller_Event::getInstance()->sendNotifications(false);
86         
87         if (! empty($this->_eventIdsToDelete)) {
88             Calendar_Controller_Event::getInstance()->delete($this->_eventIdsToDelete);
89         }
90         
91         if ($this->_emailTestClass instanceof Felamimail_Controller_MessageTest) {
92             $this->_emailTestClass->tearDown();
93         }
94     }
95     
96     /**
97      * testExternalInvitationRequestAutoProcess
98      */
99     public function testExternalInvitationRequestAutoProcess()
100     {
101         $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_request_external.ics' );
102         $iMIP = new Calendar_Model_iMIP(array(
103             'id'             => Tinebase_Record_Abstract::generateUID(),
104             'ics'            => $ics,
105             'method'         => 'REQUEST',
106             'originator'     => 'l.kneschke@caldav.org',
107         ));
108         
109         $this->_iMIPFrontend->autoProcess($iMIP);
110         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
111
112         $this->assertEquals(3, count($prepared->event->attendee));
113         $this->assertEquals('test mit extern', $prepared->event->summary);
114     }
115
116     /**
117     * testSupportedPrecondition
118     */
119     public function testUnsupportedPrecondition()
120     {
121         $iMIP = $this->_getiMIP('PUBLISH');
122             
123         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
124     
125         $this->assertEquals(1, count($prepared->preconditions));
126         $this->assertEquals('processing published events is not supported yet', $prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_SUPPORTED][0]['message']);
127         $this->assertFalse($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_SUPPORTED][0]['check']);
128     }
129     
130     /**
131      * get iMIP record from internal event
132      * 
133      * @param string $_method
134      * @param boolean $_addEventToiMIP
135      * @return Calendar_Model_iMIP
136      */
137     protected function _getiMIP($_method, $_addEventToiMIP = FALSE, $_testEmptyMethod = FALSE)
138     {
139         $event = $this->_getEvent();
140         $event = Calendar_Controller_Event::getInstance()->create($event);
141         $this->_eventIdsToDelete[] = $event->getId();
142         
143         // get iMIP invitation for event
144         $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC);
145         $vevent = $converter->fromTine20Model($event);
146         $vevent->METHOD = $_method;
147         $ics = $vevent->serialize();
148         
149         $testConfig = Zend_Registry::get('testConfig');
150         $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
151         
152         $iMIP = new Calendar_Model_iMIP(array(
153             'id'             => Tinebase_Record_Abstract::generateUID(),
154             'ics'            => $ics,
155             'method'         => ($_testEmptyMethod) ? NULL : $_method,
156             'originator'     => $email,
157         ));
158         
159         if ($_addEventToiMIP) {
160             $iMIP->event = $event;
161         }
162         
163         return $iMIP;
164     }
165     
166     /**
167      * testInternalInvitationRequestAutoProcess
168      */
169     public function testInternalInvitationRequestAutoProcess()
170     {
171         $iMIP = $this->_getiMIP('REQUEST');
172         
173         $this->_iMIPFrontend->autoProcess($iMIP);
174         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
175         
176         $this->assertEquals(2, count($prepared->event->attendee), 'expected 2 attendee');
177         $this->assertEquals('Sleep very long', $prepared->event->summary);
178         $this->assertTrue(empty($prepared->preconditions));
179     }
180
181     /**
182     * testInternalInvitationRequestAutoProcessOwnStatusAlreadySet
183     */
184     public function testInternalInvitationRequestPreconditionOwnStatusAlreadySet()
185     {
186         $iMIP = $this->_getiMIP('REQUEST', TRUE);
187         // set own status
188         $ownAttender = Calendar_Model_Attender::getOwnAttender($iMIP->getEvent()->attendee);
189         $ownAttender->status = Calendar_Model_Attender::STATUS_ACCEPTED;
190         Calendar_Controller_Event::getInstance()->attenderStatusUpdate($iMIP->getEvent(), $ownAttender, $ownAttender->status_authkey);
191         
192         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
193         $this->assertFalse(empty($prepared->preconditions));
194         $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_RECENT]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_RECENT, $prepared->preconditions)));
195     }
196     
197     /**
198     * returns a simple event
199     *
200     * @return Calendar_Model_Event
201     */
202     protected function _getEvent()
203     {
204         return new Calendar_Model_Event(array(
205             'summary'     => 'Sleep very long',
206             'dtstart'     => '2012-03-25 01:00:00',
207             'dtend'       => '2012-03-25 11:15:00',
208             'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ... not.',
209             'attendee'    => $this->_getAttendee(),
210             'organizer'   => Tinebase_Core::getUser()->contact_id,
211             'uid'         => Calendar_Model_Event::generateUID(),
212         ));
213     }
214     
215     /**
216      * get test attendee
217      *
218      * @return Tinebase_Record_RecordSet
219      */
220     protected function _getAttendee()
221     {
222         $personas = Zend_Registry::get('personas');
223         $sclever = $personas['sclever'];
224         
225         return new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
226             array(
227                 'user_id'        => Tinebase_Core::getUser()->contact_id,
228                 'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
229                 'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
230                 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
231             ),
232             array(
233                 'user_id'        => $sclever->contact_id,
234                 'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
235                 'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
236                 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
237             ),
238         ));
239     }
240     
241     /**
242      * testExternalInvitationRequestProcess
243      * - uses felamimail to cache external invitation message
244      * 
245      * -> external invitation requests are not supported atm
246      */
247     public function testExternalInvitationRequestProcess()
248     {
249         $complete = $this->_addImipMessageToEmailCache();
250         
251         $iMIP = $complete->preparedParts->getFirstRecord()->preparedData;
252         
253         Calendar_Controller_EventNotificationsTests::flushMailer();
254         $result = $this->_iMIPFrontend->process($iMIP, Calendar_Model_Attender::STATUS_ACCEPTED);
255         $this->_iMIPFrontend->prepareComponent($iMIP);
256         $this->_eventIdsToDelete[] = $iMIP->event->getId();
257         
258         // assert external organizer
259         $this->assertEquals('l.kneschke@caldav.org', $iMIP->event->organizer->email, 'wrong organizer');
260         $this->assertTrue(empty($iMIP->event->organizer->account_id), 'organizer must not have an account');
261         
262         // assert attendee
263         $this->assertEquals(1, count($iMIP->event->attendee), 'all attendee but curruser must be whiped');
264         $this->assertEquals($this->_getEmailAddress(), $iMIP->event->attendee->getFirstRecord()->user_id->email, 'wrong attendee mail');
265         $this->assertEquals(Tinebase_Core::getUser()->getId(), $iMIP->event->attendee->getFirstRecord()->user_id->account_id, 'wrong attendee');
266         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $iMIP->event->attendee->getFirstRecord()->status);
267         
268         // assert REPLY message
269         $messages = Calendar_Controller_EventNotificationsTests::getMessages();
270         $this->assertEquals(1, count($messages), 'only one mails should be send');
271         $this->assertTrue(in_array('l.kneschke@caldav.org', $messages[0]->getRecipients()), 'organizer is not a receipient');
272         $this->assertContains('METHOD:REPLY', var_export($messages[0], TRUE), 'method missing');
273     }
274     
275     /**
276      * adds new imip message to Felamimail cache
277      * 
278      * @return Felamimail_Model_Message
279      */
280     protected function _addImipMessageToEmailCache()
281     {
282         $this->_checkIMAPConfig();
283         
284         // handle message with fmail (add to cache)
285         $message = $this->_emailTestClass->messageTestHelper('calendar_request.eml', NULL, NULL, array('unittest@tine20.org', $this->_getEmailAddress()));
286         return Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
287     }
288     
289     /**
290      * testDisabledExternalImip
291      */
292     public function testDisabledExternalImip()
293     {
294         Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, true);
295         $complete = $this->_addImipMessageToEmailCache();
296         $fmailJson = new Felamimail_Frontend_Json();
297         $jsonMessage = $fmailJson->getMessage($complete->getId());
298         Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, false);
299         $this->assertEmpty($jsonMessage['preparedParts']);
300     }
301     
302     /**
303      * check IMAP config and marks test as skipped if no IMAP backend is configured
304      */
305     protected function _checkIMAPConfig()
306     {
307         $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP);
308         if (! $imapConfig || ! isset($imapConfig->useSystemAccount)
309             || $imapConfig->useSystemAccount != TRUE
310             || ! $this->_emailTestClass instanceof Felamimail_Controller_MessageTest
311         ) {
312             $this->markTestSkipped('IMAP backend not configured');
313         }
314     }
315
316     /**
317      * testExternalPublishProcess
318      * - uses felamimail to cache external publish message
319      * 
320      * NOTE: meetup sends REQUEST w.o. attendee. We might think of autoconvert this to PUBLISH
321      */
322     public function testExternalPublishProcess()
323     {
324         $this->_checkIMAPConfig();
325         
326         // handle message with fmail (add to cache)
327         $message = $this->_emailTestClass->messageTestHelper('meetup.eml');
328         $complete = Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
329         
330         $iMIP = $complete->preparedParts->getFirstRecord()->preparedData;
331         
332         $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: ATTENDEE');
333         $result = $this->_iMIPFrontend->process($iMIP);
334     }
335
336     /**
337      * testInternalInvitationRequestProcess
338      */
339     public function testInternalInvitationRequestProcess()
340     {
341         $iMIP = $this->_getiMIP('REQUEST');
342         $result = $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_TENTATIVE);
343         
344         $event = Calendar_Controller_MSEventFacade::getInstance()->lookupExistingEvent($iMIP->getEvent());
345         
346         $attender = Calendar_Model_Attender::getOwnAttender($event->attendee);
347         $this->assertEquals(Calendar_Model_Attender::STATUS_TENTATIVE, $attender->status);
348     }
349
350     /**
351      * testEmptyMethod
352      */
353     public function testEmptyMethod()
354     {
355         $iMIP = $this->_getiMIP('REQUEST', FALSE, TRUE);
356         
357         $this->assertEquals('REQUEST', $iMIP->method);
358     }
359     
360     /**
361      * testInvitationInternalReplyPreconditions
362      */
363     public function testInvitationInternalReplyPreconditions()
364     {
365         $iMIP = $this->_getiMIP('REPLY');
366         $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
367         
368         $this->assertFalse(empty($prepared->preconditions), 'empty preconditions');
369         $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_TOPROCESS]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_TOPROCESS, $prepared->preconditions)), 'missing PRECONDITION_TOPROCESS');
370     }
371     
372     /**
373      * test no seq update
374      * test no notifications
375      *
376     public function testInvitationInternalReplyAutoProcess()
377     {
378         // flush mailer
379         if (isset(Tinebase_Core::getConfig()->actionqueue)) {
380             Tinebase_ActionQueue::getInstance()->processQueue(10000);
381         }
382         Tinebase_Smtp::getDefaultTransport()->flush();
383         
384         $iMIP = $this->_getiMIP('REPLY', TRUE);
385         $event = $iMIP->getEvent();
386         
387         print_r($event->getId());
388         try {
389             $this->_iMIPFrontend->autoProcess($iMIP);
390         } catch (Exception $e) {
391             $this->fail('autoProcess throwed Exception');
392         }
393     }
394     */
395     
396     /**
397      * testInvitationExternalReply
398      */
399     public function testInvitationExternalReply()
400     {
401         $testConfig = Zend_Registry::get('testConfig');
402         $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
403         
404         $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_reply_external_accepted.ics' );
405         $ics = preg_replace('/unittest@tine20\.org/', $email, $ics);
406         
407         $iMIP = new Calendar_Model_iMIP(array(
408             'id'             => Tinebase_Record_Abstract::generateUID(),
409             'ics'            => $ics,
410             'method'         => 'REPLY',
411             'originator'     => 'mail@corneliusweiss.de',
412         ));
413         
414         $this->assertEquals(1, $iMIP->getEvent()->seq);
415         $this->assertTrue(! empty($iMIP->getEvent()->last_modified_time));
416         
417         // force creation of external attendee
418         $externalAttendee = new Calendar_Model_Attender(array(
419             'user_type'     => Calendar_Model_Attender::USERTYPE_USER,
420             'user_id'       => $iMIP->getEvent()->attendee->getFirstRecord()->user_id,
421             'status'        => Calendar_Model_Attender::STATUS_NEEDSACTION
422         ));
423         
424         // create matching event
425         $event = new Calendar_Model_Event(array(
426             'summary'     => 'TEST7',
427             'dtstart'     => '2011-11-30 14:00:00',
428             'dtend'       => '2011-11-30 15:00:00',
429             'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ...',
430             'attendee'    => $this->_getAttendee(),
431             'organizer'   => Tinebase_Core::getUser()->contact_id,
432             'uid'         => 'a8d10369e051094ae9322bd65e8afecac010bfc8',
433         ));
434         $event->attendee->addRecord($externalAttendee);
435         $event = Calendar_Controller_Event::getInstance()->create($event);
436         $this->_eventIdsToDelete[] = $event->getId();
437         
438         // TEST NORMAL REPLY
439         try {
440             $this->_iMIPFrontend->autoProcess($iMIP);
441         } catch (Exception $e) {
442             $this->fail('TEST NORMAL REPLY autoProcess throws Exception: ' . $e);
443         }
444         unset($iMIP->existing_event);
445         
446         $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
447         $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
448         
449         $this->assertEquals(3, count($updatedEvent->attendee));
450         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
451     
452         // TEST ACCEPTABLE NON RECENT REPLY
453         $updatedExternalAttendee->status = Calendar_Model_Attender::STATUS_NEEDSACTION;
454         Calendar_Controller_Event::getInstance()->attenderStatusUpdate($updatedEvent, $updatedExternalAttendee, $updatedExternalAttendee->status_authkey);
455         try {
456             $iMIP->preconditionsChecked = false;
457             $this->_iMIPFrontend->autoProcess($iMIP);
458         } catch (Exception $e) {
459             $this->fail('TEST ACCEPTABLE NON RECENT REPLY autoProcess throws Exception: ' . $e);
460         }
461         unset($iMIP->existing_event);
462         
463         $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
464         $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
465         
466         $this->assertEquals(3, count($updatedEvent->attendee));
467         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
468     
469         // TEST NON ACCEPTABLE NON RECENT REPLY
470         $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: RECENT');
471         $iMIP->preconditionsChecked = false;
472         $this->_iMIPFrontend->autoProcess($iMIP);
473     }
474
475     /**
476      * testInvitationCancel
477      * 
478       * @todo implement
479       */
480      public function testInvitationCancel()
481      {
482         $this->markTestIncomplete('implement me');
483      }
484     
485     /**
486       * testInvitationCancel
487       * 
488       * @todo implement
489       */
490      public function testOrganizerSendBy()
491      {
492          $this->markTestIncomplete('implement me');
493      }
494 }