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 $messages = Calendar_Controller_EventNotificationsTests::getMessages();
311 $this->assertEquals(1, count($messages), 'exactly one mail should be send');
312 $this->assertTrue(in_array('l.kneschke@caldav.org', $messages[0]->getRecipients()), 'organizer is not a receipient');
313 $this->assertContains('accepted', $messages[0]->getSubject(), 'wrong subject');
314 $this->assertContains('METHOD:REPLY', var_export($messages[0], TRUE), 'method missing');
315 $this->assertContains('SEQUENCE:0', var_export($messages[0], TRUE), 'external sequence has not been keepted');
319 * adds new imip message to Felamimail cache
321 * @return Felamimail_Model_Message
323 protected function _addImipMessageToEmailCache()
325 $this->_checkIMAPConfig();
327 // handle message with fmail (add to cache)
328 $message = $this->_emailTestClass->messageTestHelper('calendar_request.eml', NULL, NULL, array('unittest@tine20.org', $this->_getEmailAddress()));
329 return Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
333 * testDisabledExternalImip
335 public function testDisabledExternalImip()
337 Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, true);
338 $complete = $this->_addImipMessageToEmailCache();
339 $fmailJson = new Felamimail_Frontend_Json();
340 $jsonMessage = $fmailJson->getMessage($complete->getId());
341 Calendar_Config::getInstance()->set(Calendar_Config::DISABLE_EXTERNAL_IMIP, false);
342 $this->assertEmpty($jsonMessage['preparedParts']);
346 * check IMAP config and marks test as skipped if no IMAP backend is configured
348 protected function _checkIMAPConfig()
350 $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP);
351 if (! $imapConfig || ! isset($imapConfig->useSystemAccount)
352 || $imapConfig->useSystemAccount != TRUE
353 || ! $this->_emailTestClass instanceof Felamimail_Controller_MessageTest
355 $this->markTestSkipped('IMAP backend not configured');
360 * testExternalPublishProcess
361 * - uses felamimail to cache external publish message
363 * NOTE: meetup sends REQUEST w.o. attendee. We might think of autoconvert this to PUBLISH
365 public function testExternalPublishProcess()
367 $this->_checkIMAPConfig();
369 // handle message with fmail (add to cache)
370 $message = $this->_emailTestClass->messageTestHelper('meetup.eml');
371 $complete = Felamimail_Controller_Message::getInstance()->getCompleteMessage($message);
373 $iMIP = $complete->preparedParts->getFirstRecord()->preparedData;
375 $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: ATTENDEE');
376 $result = $this->_iMIPFrontend->process($iMIP);
380 * testInternalInvitationRequestProcess
382 public function testInternalInvitationRequestProcess()
384 $iMIP = $this->_getiMIP('REQUEST');
385 $result = $this->_iMIPFrontendMock->process($iMIP, Calendar_Model_Attender::STATUS_TENTATIVE);
387 $event = Calendar_Controller_MSEventFacade::getInstance()->lookupExistingEvent($iMIP->getEvent());
389 $attender = Calendar_Model_Attender::getOwnAttender($event->attendee);
390 $this->assertEquals(Calendar_Model_Attender::STATUS_TENTATIVE, $attender->status);
396 public function testEmptyMethod()
398 $iMIP = $this->_getiMIP('REQUEST', FALSE, TRUE);
400 $this->assertEquals('REQUEST', $iMIP->method);
404 * testInternalInvitationReplyPreconditions
406 * an internal reply does not need to be processed of course
408 public function testInternalInvitationReplyPreconditions()
410 $iMIP = $this->_getiMIP('REPLY');
411 $prepared = $this->_iMIPFrontend->prepareComponent($iMIP);
413 $this->assertFalse(empty($prepared->preconditions), 'empty preconditions');
414 $this->assertTrue((isset($prepared->preconditions[Calendar_Model_iMIP::PRECONDITION_TOPROCESS]) || array_key_exists(Calendar_Model_iMIP::PRECONDITION_TOPROCESS, $prepared->preconditions)), 'missing PRECONDITION_TOPROCESS');
418 * testInternalInvitationReplyAutoProcess
420 * an internal reply does not need to be processed of course
422 public function testInternalInvitationReplyAutoProcess()
425 if (isset(Tinebase_Core::getConfig()->actionqueue)) {
426 Tinebase_ActionQueue::getInstance()->processQueue(10000);
428 Tinebase_Smtp::getDefaultTransport()->flush();
430 $iMIP = $this->_getiMIP('REPLY', TRUE);
431 $event = $iMIP->getEvent();
434 $this->_iMIPFrontend->autoProcess($iMIP);
435 } catch (Exception $e) {
436 $this->assertContains('TOPROCESS', $e->getMessage());
440 $this->fail("autoProcess did not throw TOPROCESS Exception $e");
444 * testInvitationExternalReply
446 public function testInvitationExternalReply()
448 $testConfig = Zend_Registry::get('testConfig');
449 $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
451 $ics = file_get_contents(dirname(__FILE__) . '/files/invitation_reply_external_accepted.ics' );
452 $ics = preg_replace('/unittest@tine20\.org/', $email, $ics);
454 $iMIP = new Calendar_Model_iMIP(array(
455 'id' => Tinebase_Record_Abstract::generateUID(),
458 'originator' => 'mail@corneliusweiss.de',
461 $this->assertEquals(1, $iMIP->getEvent()->seq);
462 $this->assertTrue(! empty($iMIP->getEvent()->last_modified_time));
464 // force creation of external attendee
465 $externalAttendee = new Calendar_Model_Attender(array(
466 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
467 'user_id' => $iMIP->getEvent()->attendee->getFirstRecord()->user_id,
468 'status' => Calendar_Model_Attender::STATUS_NEEDSACTION
471 // create matching event
472 $event = new Calendar_Model_Event(array(
473 'summary' => 'TEST7',
474 'dtstart' => '2011-11-30 14:00:00',
475 'dtend' => '2011-11-30 15:00:00',
476 'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise ...',
477 'attendee' => $this->_getAttendee(),
478 'organizer' => Tinebase_Core::getUser()->contact_id,
479 'uid' => 'a8d10369e051094ae9322bd65e8afecac010bfc8',
481 $event->attendee->addRecord($externalAttendee);
482 $event = Calendar_Controller_Event::getInstance()->create($event);
483 $this->_eventIdsToDelete[] = $event->getId();
487 $this->_iMIPFrontend->autoProcess($iMIP);
488 } catch (Exception $e) {
489 $this->fail('TEST NORMAL REPLY autoProcess throws Exception: ' . $e);
491 unset($iMIP->existing_event);
493 $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
494 $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
496 $this->assertEquals(3, count($updatedEvent->attendee));
497 $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
499 // TEST ACCEPTABLE NON RECENT REPLY
500 $updatedExternalAttendee->status = Calendar_Model_Attender::STATUS_NEEDSACTION;
501 Calendar_Controller_Event::getInstance()->attenderStatusUpdate($updatedEvent, $updatedExternalAttendee, $updatedExternalAttendee->status_authkey);
503 $iMIP->preconditionsChecked = false;
504 $this->_iMIPFrontend->autoProcess($iMIP);
505 } catch (Exception $e) {
506 $this->fail('TEST ACCEPTABLE NON RECENT REPLY autoProcess throws Exception: ' . $e);
508 unset($iMIP->existing_event);
510 $updatedEvent = Calendar_Controller_Event::getInstance()->get($event->getId());
511 $updatedExternalAttendee = Calendar_Model_Attender::getAttendee($updatedEvent->attendee, $externalAttendee);
513 $this->assertEquals(3, count($updatedEvent->attendee));
514 $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedExternalAttendee->status, 'status not updated');
516 // TEST NON ACCEPTABLE NON RECENT REPLY
517 $this->setExpectedException('Calendar_Exception_iMIP', 'iMIP preconditions failed: RECENT');
518 $iMIP->preconditionsChecked = false;
519 $this->_iMIPFrontend->autoProcess($iMIP);
523 * testInvitationCancel
527 public function testInvitationCancel()
529 $this->markTestIncomplete('implement me');
533 * testInvitationCancel
537 public function testOrganizerSendBy()
539 $this->markTestIncomplete('implement me');