3 * Tine 2.0 - http://www.tine20.org
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>
10 * @todo add tests testInvitationCancel and testOrganizerSendBy
14 * Test class for Calendar_Frontend_iMIP
16 class Calendar_Frontend_iMIPTest extends TestCase
19 * event ids that should be deleted in tearDown
23 protected $_eventIdsToDelete = array();
26 * iMIP frontent to be tested
28 * @var Calendar_Frontend_iMIP
30 protected $_iMIPFrontend = NULL;
33 * iMIP frontent to be tested
35 * @var Calendar_Frontend_iMIPMock
37 protected $_iMIPFrontendMock = NULL;
42 * @var Felamimail_Controller_MessageTest
44 protected $_emailTestClass;
47 * Runs the test methods of this class.
52 public static function main()
54 $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 Calendar iMIP Tests');
55 PHPUnit_TextUI_TestRunner::run($suite);
59 * Sets up the fixture.
60 * This method is called before a test is executed.
64 protected function setUp()
66 Calendar_Controller_Event::getInstance()->sendNotifications(true);
68 Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, false);
70 $this->_iMIPFrontend = new Calendar_Frontend_iMIP();
71 $this->_iMIPFrontendMock = new Calendar_Frontend_iMIPMock();
74 $this->_emailTestClass = new Felamimail_Controller_MessageTest();
75 $this->_emailTestClass->setup();
76 } catch (Exception $e) {
82 * Tears down the fixture
83 * This method is called after a test is executed.
87 protected function tearDown()
89 Calendar_Controller_Event::getInstance()->sendNotifications(false);
91 if (! empty($this->_eventIdsToDelete)) {
92 Calendar_Controller_Event::getInstance()->delete($this->_eventIdsToDelete);
95 if ($this->_emailTestClass instanceof Felamimail_Controller_MessageTest) {
96 $this->_emailTestClass->tearDown();
101 * testExternalInvitationRequestAutoProcess
103 public function testExternalInvitationRequestAutoProcess()
105 $ics = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/files/invitation_request_external.ics' );
106 $iMIP = new Calendar_Model_iMIP(array(
107 'id' => Tinebase_Record_Abstract::generateUID(),
109 'method' => 'REQUEST',
110 'originator' => 'l.kneschke@caldav.org',
113 $this->_iMIPFrontend->autoProcess($iMIP);
114 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
116 $this->assertEmpty($prepared->existing_event, 'there should be no existing event');
117 $this->assertEmpty($prepared->preconditions, 'no preconditions should be raised');
118 $this->assertEquals(5, count($prepared->event->attendee));
119 $this->assertEquals('test mit extern', $prepared->event->summary);
125 * testSupportedPrecondition
127 public function testUnsupportedPrecondition()
129 $iMIP = $this->_getiMIP('PUBLISH');
131 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
133 $this->assertEquals(1, count($prepared->preconditions));
134 $this->assertEquals('processing published events is not supported yet', $prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_SUPPORTED][0]['message']);
135 $this->assertFalse($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_SUPPORTED][0]['check']);
139 * get iMIP record from internal event
141 * @param string $_method
142 * @param boolean $_addEventToiMIP
143 * @return Calendar_Model_iMIP
145 protected function _getiMIP($_method, $_addEventToiMIP = FALSE, $_testEmptyMethod = FALSE)
147 $testConfig = Zend_Registry::get('testConfig');
148 $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
150 $event = $this->_getEvent();
151 $event = Calendar_Controller_Event::getInstance()->create($event);
152 $this->_eventIdsToDelete[] = $event->getId();
154 if ($_method == 'REPLY') {
155 $personas = Zend_Registry::get('personas');
156 $sclever = $personas['sclever'];
158 $scleverAttendee = $event->attendee
159 ->filter('status', Calendar_Model_Attender::STATUS_NEEDSACTION)
162 $scleverAttendee->status = Calendar_Model_Attender::STATUS_ACCEPTED;
163 Calendar_Controller_Event::getInstance()->attenderStatusUpdate($event, $scleverAttendee, $scleverAttendee->status_authkey);
164 $event = Calendar_Controller_Event::getInstance()->get($event->getId());
165 $email = $sclever->accountEmailAddress;
168 // get iMIP invitation for event
169 $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC);
170 $vevent = $converter->fromTine20Model($event);
171 $vevent->METHOD = $_method;
172 $ics = $vevent->serialize();
174 $iMIP = new Calendar_Model_iMIP(array(
175 'id' => Tinebase_Record_Abstract::generateUID(),
177 'method' => ($_testEmptyMethod) ? NULL : $_method,
178 'originator' => $email,
181 if ($_addEventToiMIP) {
182 $iMIP->event = $event;
189 * testInternalInvitationRequestAutoProcess
191 public function testInternalInvitationRequestAutoProcess()
193 $iMIP = $this->_getiMIP('REQUEST');
195 $this->_iMIPFrontend->autoProcess($iMIP);
196 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
198 $this->assertEquals(2, count($prepared->event->attendee), 'expected 2 attendee');
199 $this->assertEquals('Sleep very long', $prepared->event->summary);
200 $this->assertTrue(empty($prepared->preconditions));
204 * testInternalInvitationRequestAutoProcessOwnStatusAlreadySet
206 public function testInternalInvitationRequestPreconditionOwnStatusAlreadySet()
208 $iMIP = $this->_getiMIP('REQUEST', TRUE);
211 $ownAttender = Calendar_Model_Attender::getOwnAttender($iMIP->getEvent()->attendee);
212 $ownAttender->status = Calendar_Model_Attender::STATUS_TENTATIVE;
213 Calendar_Controller_Event::getInstance()->attenderStatusUpdate($iMIP->getEvent(), $ownAttender, $ownAttender->status_authkey);
215 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
216 $this->assertTrue(empty($prepared->preconditions), "it's ok to reanswer without reschedule!");
219 $event = Calendar_Controller_Event::getInstance()->get($prepared->existing_event->getId());
220 $event->dtstart->addHour(2);
221 $event->dtend->addHour(2);
222 Calendar_Controller_Event::getInstance()->update($event, false);
224 $iMIP->getExistingEvent(true);
225 $iMIP->preconditionsChecked = false;
226 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
228 $this->assertFalse(empty($prepared->preconditions), 'do not accept this iMIP after reshedule');
229 $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_RECENT]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_RECENT, $prepared->preconditions)));
233 * returns a simple event
235 * @return Calendar_Model_Event
237 protected function _getEvent()
239 return new Calendar_Model_Event(array(
240 'summary' => 'Sleep very long',
241 'dtstart' => '2012-03-25 01:00:00',
242 'dtend' => '2012-03-25 11:15:00',
243 'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ... not.',
244 'attendee' => $this->_getAttendee(),
245 'organizer' => Tinebase_Core::getUser()->contact_id,
246 'uid' => Calendar_Model_Event::generateUID(),
253 * @return Tinebase_Record_RecordSet
255 protected function _getAttendee()
257 $personas = Zend_Registry::get('personas');
258 $sclever = $personas['sclever'];
260 return new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
262 'user_id' => Tinebase_Core::getUser()->contact_id,
263 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
264 'role' => Calendar_Model_Attender::ROLE_REQUIRED,
265 'status' => Calendar_Model_Attender::STATUS_ACCEPTED,
266 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
269 'user_id' => $sclever->contact_id,
270 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
271 'role' => Calendar_Model_Attender::ROLE_REQUIRED,
272 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
278 * testExternalInvitationRequestProcess
280 public function testExternalInvitationRequestProcess()
282 $ics = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/files/invitation_request_external.ics' );
283 $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);
284 $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);
286 $iMIP = new Calendar_Model_iMIP(array(
287 'id' => Tinebase_Record_Abstract::generateUID(),
289 'method' => 'REQUEST',
290 'originator' => 'l.kneschke@caldav.org',
293 Calendar_Controller_EventNotificationsTests::flushMailer();
294 $result = $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_ACCEPTED);
296 $this->_iMIPFrontend->prepareComponent($iMIP);
297 $this->_eventIdsToDelete[] = $iMIP->event->getId();
299 // assert external organizer
300 $this->assertEquals('l.kneschke@caldav.org', $iMIP->event->organizer->email, 'wrong organizer');
301 $this->assertTrue(empty($iMIP->event->organizer->account_id), 'organizer must not have an account');
304 $ownAttendee = Calendar_Model_Attender::getOwnAttender($iMIP->event->attendee);
305 $this->assertTrue(!! $ownAttendee, 'own attendee missing');
306 $this->assertEquals(5, count($iMIP->event->attendee), 'all attendee must be keeped');
307 $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $ownAttendee->status, 'must be ACCEPTED');
309 // assert REPLY message to organizer only
310 $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP);
311 $mailer = Calendar_Controller_EventNotificationsTests::getMailer();
312 if ($mailer instanceof Zend_Mail_Transport_Array || (isset($smtpConfig->from) && ! empty($smtpConfig->from))) {
313 $messages = Calendar_Controller_EventNotificationsTests::getMessages();
314 $this->assertEquals(1, count($messages), 'exactly one mail should be send');
315 $this->assertTrue(in_array('l.kneschke@caldav.org', $messages[0]->getRecipients()), 'organizer is not a receipient');
316 $this->assertContains('accepted', $messages[0]->getSubject(), 'wrong subject');
317 $this->assertContains('METHOD:REPLY', var_export($messages[0], TRUE), 'method missing');
318 $this->assertContains('SEQUENCE:0', var_export($messages[0], TRUE), 'external sequence has not been keepted');
323 * adds new imip message to Felamimail cache
325 * @return Felamimail_Model_Message
327 protected function _addImipMessageToEmailCache()
329 $this->_checkIMAPConfig();
331 // handle message with fmail (add to cache)
332 $message = $this->_emailTestClass->messageTestHelper('calendar_request.eml', NULL, NULL, array('unittest@tine20.org', $this->_getEmailAddress()));
333 return Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
337 * testDisabledExternalImip
339 public function testDisabledExternalImip()
341 Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, true);
342 $complete = $this->_addImipMessageToEmailCache();
343 $fmailJson = new Felamimail_Frontend_Json();
344 $jsonMessage = $fmailJson->getMessage($complete->getId());
345 Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, false);
346 $this->assertEmpty($jsonMessage['preparedParts']);
350 * check IMAP config and marks test as skipped if no IMAP backend is configured
352 protected function _checkIMAPConfig()
354 $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP);
355 if (! $imapConfig || ! isset($imapConfig->useSystemAccount)
356 || $imapConfig->useSystemAccount != TRUE
357 || ! $this->_emailTestClass instanceof Felamimail_Controller_MessageTest
359 $this->markTestSkipped('IMAP backend not configured');
364 * testExternalPublishProcess
365 * - uses felamimail to cache external publish message
367 * NOTE: meetup sends REQUEST w.o. attendee. We might think of autoconvert this to PUBLISH
369 public function testExternalPublishProcess()
371 $this->_checkIMAPConfig();
373 // handle message with fmail (add to cache)
374 $message = $this->_emailTestClass->messageTestHelper('meetup.eml');
375 $complete = Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
377 $iMIP = $complete->preparedParts->getFirstRecord()->preparedData;
379 $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: ATTENDEE');
380 $result = $this->_iMIPFrontend->process($iMIP);
384 * testInternalInvitationRequestProcess
386 public function testInternalInvitationRequestProcess()
388 $iMIP = $this->_getiMIP('REQUEST');
389 $result = $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_TENTATIVE);
391 $event = Calendar_Controller_MSEventFacade::getInstance()->lookupExistingEvent($iMIP->getEvent());
393 $attender = Calendar_Model_Attender::getOwnAttender($event->attendee);
394 $this->assertEquals(Calendar_Model_Attender::STATUS_TENTATIVE, $attender->status);
400 public function testEmptyMethod()
402 $iMIP = $this->_getiMIP('REQUEST', FALSE, TRUE);
404 $this->assertEquals('REQUEST', $iMIP->method);
408 * testInternalInvitationReplyPreconditions
410 * an internal reply does not need to be processed of course
412 public function testInternalInvitationReplyPreconditions()
414 $iMIP = $this->_getiMIP('REPLY');
415 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
417 $this->assertFalse(empty($prepared->preconditions), 'empty preconditions');
418 $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_TOPROCESS]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_TOPROCESS, $prepared->preconditions)), 'missing PRECONDITION_TOPROCESS');
422 * testInternalInvitationReplyAutoProcess
424 * an internal reply does not need to be processed of course
426 public function testInternalInvitationReplyAutoProcess()
429 if (isset(Tinebase_Core::getConfig()->actionqueue)) {
430 Tinebase_ActionQueue::getInstance()->processQueue(10000);
432 Tinebase_Smtp::getDefaultTransport()->flush();
434 $iMIP = $this->_getiMIP('REPLY', TRUE);
435 $event = $iMIP->getEvent();
438 $this->_iMIPFrontend->autoProcess($iMIP);
439 } catch (Exception $e) {
440 $this->assertContains('TOPROCESS', $e->getMessage());
444 $this->fail("autoProcess did not throw TOPROCESS Exception $e");
448 * testInvitationExternalReply
450 public function testInvitationExternalReply()
452 $testConfig = Zend_Registry::get('testConfig');
453 $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
455 $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_reply_external_accepted.ics' );
456 $ics = preg_replace('/unittest@tine20\.org/', $email, $ics);
458 $iMIP = new Calendar_Model_iMIP(array(
459 'id' => Tinebase_Record_Abstract::generateUID(),
462 'originator' => 'mail@corneliusweiss.de',
465 $this->assertEquals(1, $iMIP->getEvent()->seq);
466 $this->assertTrue(! empty($iMIP->getEvent()->last_modified_time));
468 // force creation of external attendee
469 $externalAttendee = new Calendar_Model_Attender(array(
470 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
471 'user_id' => $iMIP->getEvent()->attendee->getFirstRecord()->user_id,
472 'status' => Calendar_Model_Attender::STATUS_NEEDSACTION
475 // create matching event
476 $event = new Calendar_Model_Event(array(
477 'summary' => 'TEST7',
478 'dtstart' => '2011-11-30 14:00:00',
479 'dtend' => '2011-11-30 15:00:00',
480 'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ...',
481 'attendee' => $this->_getAttendee(),
482 'organizer' => Tinebase_Core::getUser()->contact_id,
483 'uid' => 'a8d10369e051094ae9322bd65e8afecac010bfc8',
485 $event->attendee->addRecord($externalAttendee);
486 $event = Calendar_Controller_Event::getInstance()->create($event);
487 $this->_eventIdsToDelete[] = $event->getId();
491 $this->_iMIPFrontend->autoProcess($iMIP);
492 } catch (Exception $e) {
493 $this->fail('TEST NORMAL REPLY autoProcess throws Exception: ' . $e);
495 unset($iMIP->existing_event);
497 $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
498 $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
500 $this->assertEquals(3, count($updatedEvent->attendee));
501 $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
503 // TEST ACCEPTABLE NON RECENT REPLY
504 $updatedExternalAttendee->status = Calendar_Model_Attender::STATUS_NEEDSACTION;
505 Calendar_Controller_Event::getInstance()->attenderStatusUpdate($updatedEvent, $updatedExternalAttendee, $updatedExternalAttendee->status_authkey);
507 $iMIP->preconditionsChecked = false;
508 $this->_iMIPFrontend->autoProcess($iMIP);
509 } catch (Exception $e) {
510 $this->fail('TEST ACCEPTABLE NON RECENT REPLY autoProcess throws Exception: ' . $e);
512 unset($iMIP->existing_event);
514 $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
515 $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
517 $this->assertEquals(3, count($updatedEvent->attendee));
518 $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
520 // TEST NON ACCEPTABLE NON RECENT REPLY
521 $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: RECENT');
522 $iMIP->preconditionsChecked = false;
523 $this->_iMIPFrontend->autoProcess($iMIP);
527 * testInvitationCancel
531 public function testInvitationCancel()
533 $this->markTestIncomplete('implement me');
537 * testInvitationCancel
541 public function testOrganizerSendBy()
543 $this->markTestIncomplete('implement me');