1<?php
2/**
3 * Unit tests Horde_ActiveSync_Message_Appointment objects.
4 *
5 * @author Michael J. Rubinsky <mrubinsk@horde.org>
6 * @category Horde
7 * @package ActiveSync
8 */
9class Horde_ActiveSync_AppointmentTest extends Horde_Test_Case
10{
11
12    protected $_oldtz;
13
14    public function setUp()
15    {
16        $this->_oldtz = date_default_timezone_get();
17        date_default_timezone_set('America/New_York');
18    }
19
20    public function tearDown()
21    {
22        date_default_timezone_set($this->_oldtz);
23    }
24
25    /**
26     * Checks that setting/getting non-existant properties throws an exception.
27     */
28    public function testEncoding()
29    {
30        $this->markTestIncomplete('Needs updated fixture.');
31        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
32
33        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
34        $appt->setSubject('Event Title');
35        $appt->setBody('Event Description');
36        $appt->setLocation('Philadelphia, PA');
37        $start = new Horde_Date('2011-12-01T15:00:00');
38        $appt->setDatetime(array(
39            'start' => $start,
40            'end' => new Horde_Date('2011-12-01T16:00:00'),
41            'allday' => false)
42        );
43        $appt->setTimezone($start);
44        $appt->setSensitivity(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL);
45        $appt->setBusyStatus(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY);
46        $appt->setDTStamp($start->timestamp());
47
48        $stream = fopen('php://memory', 'w+');
49        $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream);
50        $encoder->setLogger($logger);
51
52        $encoder->startTag(Horde_ActiveSync::SYNC_DATA);
53        $appt->encodeStream($encoder);
54        $encoder->endTag();
55        $fixture = file_get_contents(__DIR__ . '/fixtures/appointment.wbxml');
56        rewind($stream);
57        $results = stream_get_contents($stream);
58        fclose($stream);
59
60        // TODO
61        $this->assertEquals($fixture, $results);
62    }
63
64    public function testDecoding()
65    {
66        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
67        $stream = fopen(__DIR__ . '/fixtures/appointment.wbxml', 'r+');
68        $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream);
69        $decoder->setLogger($logger);
70
71        $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA);
72        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
73        $appt->decodeStream($decoder);
74        fclose($stream);
75        $decoder->getElementEndTag();
76
77        $this->assertEquals('Event Title', $appt->subject);
78        $this->assertEquals('Event Description', $appt->body);
79        $this->assertEquals('Philadelphia, PA', $appt->location);
80        $this->assertEquals(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL, (integer)$appt->sensitivity);
81        $this->assertEquals(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY, (integer)$appt->busystatus);
82
83        $start = clone($appt->starttime);
84        // Ensure it's UTC
85        $this->assertEquals('UTC', $start->timezone);
86
87        //...and correct.
88        $start->setTimezone('America/New_York');
89        $this->assertEquals('2011-12-01 15:00:00', (string)$start);
90    }
91
92    public function testEncodingRecurrence()
93    {
94        $this->markTestIncomplete('Needs updated fixture.');
95        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
96
97        // Every other week recurrence, on thursday, no end.
98        $r = new Horde_Date_Recurrence('2011-12-01T15:00:00');
99        $r->setRecurType(Horde_Date_Recurrence::RECUR_WEEKLY);
100        $r->setRecurInterval(2);
101        $r->setRecurOnDay(Horde_Date::MASK_THURSDAY);
102
103        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
104        $appt->setSubject('Event Title');
105        $appt->setBody('Event Description');
106        $appt->setLocation('Philadelphia, PA');
107        $start = new Horde_Date('2011-12-01T15:00:00');
108        $appt->setDatetime(array(
109            'start' => $start,
110            'end' => new Horde_Date('2011-12-01T16:00:00'),
111            'allday' => false)
112        );
113        $appt->setTimezone($start);
114        $appt->setSensitivity(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL);
115        $appt->setBusyStatus(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY);
116        $appt->setDTStamp($start->timestamp());
117        $appt->setRecurrence($r);
118
119        $stream = fopen('php://memory', 'w+');
120        $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream);
121        $encoder->setLogger($logger);
122
123        $encoder->startTag(Horde_ActiveSync::SYNC_DATA);
124        $appt->encodeStream($encoder);
125        $encoder->endTag();
126        $fixture = file_get_contents(__DIR__ . '/fixtures/recurrence.wbxml');
127        rewind($stream);
128        $results = stream_get_contents($stream);
129        fclose($stream);
130
131        $this->assertEquals($fixture, $results);
132    }
133
134    public function testDecodingRecurrence()
135    {
136        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
137        // Test Decoding
138        $stream = fopen(__DIR__ . '/fixtures/recurrence.wbxml', 'r+');
139        $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream);
140
141        $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA);
142        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
143        $appt->decodeStream($decoder);
144        fclose($stream);
145        $decoder->getElementEndTag();
146
147        // Same properties that are testing in testDeoding, but test again
148        // here to be sure recurrence doesn't mess up the deocder.
149        $this->assertEquals('Event Title', $appt->subject);
150        $this->assertEquals('Event Description', $appt->body);
151        $this->assertEquals('Philadelphia, PA', $appt->location);
152        $this->assertEquals(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL, (integer)$appt->sensitivity);
153        $this->assertEquals(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY, (integer)$appt->busystatus);
154        $start = clone($appt->starttime);
155        // Ensure it's UTC
156        $this->assertEquals('UTC', $start->timezone);
157        //...and correct.
158        $start->setTimezone('America/New_York');
159        $this->assertEquals('2011-12-01 15:00:00', (string)$start);
160
161        // Recurrence properties
162        $rrule = $appt->getRecurrence();
163        $this->assertEquals('2011-12-01 15:00:00', (string)$rrule->getRecurStart()->setTimezone('America/New_York'));
164        $this->assertEquals('', (string)$rrule->getRecurEnd());
165        $this->assertEquals(Horde_Date_Recurrence::RECUR_WEEKLY, $rrule->getRecurType());
166        $this->assertEquals(2, $rrule->getRecurInterval());
167        $this->assertEquals(Horde_Date::MASK_THURSDAY, $days = $rrule->getRecurOnDays());
168    }
169
170    public function testEncodingSimpleExceptions()
171    {
172        $this->markTestIncomplete('Needs updated fixture.');
173        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
174
175        // Every other week recurrence, on thursday, no end.
176        $r = new Horde_Date_Recurrence('2011-12-01T15:00:00');
177        $r->setRecurType(Horde_Date_Recurrence::RECUR_WEEKLY);
178        $r->setRecurInterval(2);
179        $r->setRecurOnDay(Horde_Date::MASK_THURSDAY);
180        $r->addException(2011, 12, 29);
181
182        $e = new Horde_ActiveSync_Message_Exception();
183        $d = new Horde_Date('2011-12-29T15:00:00');
184        $e->setExceptionStartTime($d);
185        $e->deleted = true;
186
187        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
188        $appt->setSubject('Event Title');
189        $appt->setBody('Event Description');
190        $appt->setLocation('Philadelphia, PA');
191        $start = new Horde_Date('2011-12-01T15:00:00');
192        $appt->setDatetime(array(
193            'start' => $start,
194            'end' => new Horde_Date('2011-12-01T16:00:00'),
195            'allday' => false)
196        );
197        $appt->setTimezone($start);
198        $appt->setSensitivity(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL);
199        $appt->setBusyStatus(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY);
200        $appt->setDTStamp($start->timestamp());
201        $appt->setRecurrence($r);
202        $appt->addException($e);
203
204        $stream = fopen('php://memory', 'w+');
205        $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream);
206        $encoder->setLogger($logger);
207        $encoder->startTag(Horde_ActiveSync::SYNC_DATA);
208        $appt->encodeStream($encoder);
209        $encoder->endTag();
210
211        $fixture = file_get_contents(__DIR__ . '/fixtures/simpleexception.wbxml');
212        rewind($stream);
213        $results = stream_get_contents($stream);
214        fclose($stream);
215
216        $this->assertEquals($fixture, $results);
217    }
218
219    public function testAlldayEncoding()
220    {
221        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
222
223        // Check that the encoded wbxml looks correct.
224        $stream_out = fopen('php://memory', 'w+');
225        $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream_out);
226        $message = new Horde_ActiveSync_Message_Appointment(
227            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
228        );
229        $message->setSubject('Test Event');
230        $message->alldayevent = true;
231        $start = new Horde_Date('1970-03-20T00:00:00', 'America/New_York');
232        $end = new Horde_Date('1970-03-21T00:00:00', 'America/New_York');
233        $message->starttime = $start;
234        $message->endtime = $end;
235        $message->setTimezone($start);
236        $message->meetingstatus = Horde_ActiveSync_Message_Appointment::MEETING_NOT_MEETING;
237        $message->encodeStream($encoder);
238
239        $fixture = file_get_contents(__DIR__ . '/fixtures/allday_appointment.wbxml');
240        rewind($stream_out);
241        $this->assertEquals($fixture, stream_get_contents($stream_out));
242
243        // Make sure EAS versions work properly.
244        rewind($stream_out);
245        $message = new Horde_ActiveSync_Message_Appointment(
246            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
247        );
248        $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream_out);
249        $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA);
250        $message->decodeStream($decoder);
251        $end = $message->endtime;
252        $end->setTimezone('America/New_York');
253        $start->setTimezone('America/New_York');
254        $this->assertEquals('1970-03-21 00:00:00', (string)$end);
255        $this->assertEquals('1970-03-20 00:00:00', (string)$start);
256
257        rewind($stream_out);
258        $message = new Horde_ActiveSync_Message_Appointment(
259            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_SIXTEEN)
260        );
261        $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream_out);
262        $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA);
263        $message->decodeStream($decoder);
264        $end = $message->endtime;
265        $end->setTimezone('America/New_York');
266        $start->setTimezone('America/New_York');
267        $this->assertEquals('1970-03-21 00:00:00', (string)$end);
268        $this->assertEquals('1970-03-20 00:00:00', (string)$start);
269    }
270
271    /**
272     * Test deprecated setDatetime method since it's still used in FW_52.
273     */
274    public function testSetDatetimeAlldayHandling()
275    {
276        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
277
278        // Test the deprecated setDatetime method's ability to properly detect
279        // and set properties.
280        // Single day 00:00 to 00:00
281        $message = new Horde_ActiveSync_Message_Appointment(
282            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
283        );
284        $start = new Horde_Date('1970-03-20T00:00:00', 'America/New_York');
285        $end = new Horde_Date('1970-03-21T00:00:00', 'America/New_York');
286        $message->setDatetime(array('start' => $start, 'end' => $end));
287        $this->assertEquals(true, $message->alldayevent);
288
289        // Multiday 00:00 to 23:59
290        $message = new Horde_ActiveSync_Message_Appointment(
291            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
292        );
293        $start = new Horde_Date('1970-03-20T00:00:00', 'America/New_York');
294        $end = new Horde_Date('1970-03-21T23:59:00', 'America/New_York');
295        $message->setDatetime(array('start' => $start, 'end' => $end));
296        $this->assertEquals(true, $message->alldayevent);
297        $end = $message->endtime;
298        $end->setTimezone('America/New_York');
299        $this->assertEquals('1970-03-22 00:00:00', (string)$end);
300
301        // Single day with incorrect time part, no endtime given.
302        $message = new Horde_ActiveSync_Message_Appointment(
303            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
304        );
305        $start = new Horde_Date('1970-03-20T04:00:00', 'America/New_York');
306        $message->setDatetime(array('start' => $start, 'allday' => true));
307        $this->assertEquals(true, $message->alldayevent);
308        $start = $message->starttime;
309        $start->setTimezone('America/New_York');
310        $this->assertEquals('1970-03-20 00:00:00', (string)$start);
311        $end = $message->endtime;
312        $end->setTimezone('America/New_York');
313        $this->assertEquals('1970-03-21 00:00:00', (string)$end);
314
315        // Single day, no endtime given.
316        $message = new Horde_ActiveSync_Message_Appointment(
317            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
318        );
319        $start = new Horde_Date('1970-03-20T00:00:00', 'America/New_York');
320        $message->setDatetime(array('start' => $start, 'allday' => true));
321        $this->assertEquals(true, $message->alldayevent);
322        $end = $message->endtime;
323        $end->setTimezone('America/New_York');
324        $this->assertEquals('1970-03-21 00:00:00', (string)$end);
325
326        // Make sure non-all day events don't inadvertently get converted to one
327        $message = new Horde_ActiveSync_Message_Appointment(
328            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
329        );
330        $start = new Horde_Date('1970-03-20T05:00:00', 'America/New_York');
331        $end = new Horde_Date('1970-03-21T00:00:00', 'America/New_York');
332        $message->setDatetime(array('start' => $start, 'end' => $end));
333        $this->assertEquals(false, $message->alldayevent);
334        $start = $message->starttime;
335        $start->setTimezone('America/New_York');
336        $this->assertEquals('1970-03-20 05:00:00', (string)$start);
337
338        // Incorrect timeparts given, but allday flag is set.
339        $message = new Horde_ActiveSync_Message_Appointment(
340            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
341        );
342        $start = new Horde_Date('1970-03-20T00:00:00', 'America/New_York');
343        $end = new Horde_Date('1970-03-21T05:00:00', 'America/New_York');
344        $message->setDatetime(array('start' => $start, 'end' => $end, 'allday' => true));
345        $this->assertEquals(true, $message->alldayevent);
346        $start = $message->starttime;
347        $start->setTimezone('America/New_York');
348        $end = $message->endtime;
349        $end->setTimezone('America/New_York');
350        $this->assertEquals('1970-03-20 00:00:00', (string)$start);
351        $this->assertEquals('1970-03-22 00:00:00', (string)$end);
352
353        $message = new Horde_ActiveSync_Message_Appointment(
354            array('logger' => $logger, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)
355        );
356        $start = new Horde_Date('1970-03-20T08:00:00', 'America/New_York');
357        $end = new Horde_Date('1970-03-21T05:00:00', 'America/New_York');
358        $message->setDatetime(array('start' => $start, 'end' => $end, 'allday' => true));
359        $this->assertEquals(true, $message->alldayevent);
360        $start = $message->starttime;
361        $start->setTimezone('America/New_York');
362        $end = $message->endtime;
363        $end->setTimezone('America/New_York');
364        $this->assertEquals('1970-03-20 00:00:00', (string)$start);
365        $this->assertEquals('1970-03-22 00:00:00', (string)$end);
366    }
367
368    public function testDecodingSimpleExceptions()
369    {
370        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
371
372        // Test Decoding
373        $stream = fopen(__DIR__ . '/fixtures/simpleexception.wbxml', 'r+');
374        $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream);
375
376        $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA);
377        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
378        $appt->decodeStream($decoder);
379        fclose($stream);
380        $decoder->getElementEndTag();
381
382        // Same properties that are testing in testDeoding, but test again
383        // here to be sure recurrence doesn't mess up the deocder.
384        $this->assertEquals('Event Title', $appt->subject);
385        $this->assertEquals('Event Description', $appt->body);
386        $this->assertEquals('Philadelphia, PA', $appt->location);
387        $this->assertEquals(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL, (integer)$appt->sensitivity);
388        $this->assertEquals(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY, (integer)$appt->busystatus);
389        $start = clone($appt->starttime);
390        // Ensure it's UTC
391        $this->assertEquals('UTC', $start->timezone);
392        //...and correct.
393        $start->setTimezone('America/New_York');
394        $this->assertEquals('2011-12-01 15:00:00', (string)$start);
395
396        // Recurrence properties
397        $rrule = $appt->getRecurrence();
398        $this->assertEquals('2011-12-01 15:00:00', (string)$rrule->getRecurStart()->setTimezone('America/New_York'));
399        $this->assertEquals('', (string)$rrule->getRecurEnd());
400        $this->assertEquals(Horde_Date_Recurrence::RECUR_WEEKLY, $rrule->getRecurType());
401        $this->assertEquals(2, $rrule->getRecurInterval());
402        $this->assertEquals(Horde_Date::MASK_THURSDAY, $days = $rrule->getRecurOnDays());
403
404
405        // Ensure the exception came over (should have one, deleted exception
406        // on 2011-12-29)
407        $exceptions = $appt->getExceptions();
408        $e = array_pop($exceptions);
409        $this->assertEquals(true, (boolean)$e->deleted);
410        $dt = $e->getExceptionStartTime();
411        $rrule->addException($dt->format('Y'), $dt->format('m'), $dt->format('d'));
412
413        // This would normally be 2011-12-29, but that's an exception.
414        $date = $rrule->nextActiveRecurrence(new Horde_Date('2011-12-16'));
415        $this->assertEquals('2012-01-12 15:00:00', (string)$date);
416    }
417
418    public function testRecurrenceDSTSwitch()
419    {
420        // Recurring event starts 10/1/2011 15:00:00 EDST
421        $logger = new Horde_ActiveSync_Log_Logger(new Horde_Log_Handler_Null());
422
423        // Test Decoding
424        $stream = fopen(__DIR__ . '/fixtures/dst.wbxml', 'r+');
425        $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream);
426
427        $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA);
428        $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger));
429        $appt->decodeStream($decoder);
430        fclose($stream);
431        $decoder->getElementEndTag();
432        $rrule = $appt->getRecurrence();
433
434        // Get the next recurrence, still during EDST
435        $next = $rrule->nextActiveRecurrence(new Horde_Date('2011-10-15'));
436        $this->assertEquals('2011-10-15 15:00:00', (string)$next->setTimezone('America/New_York'));
437
438        // Now get an occurence after the transition to EST.
439        $next = $rrule->nextActiveRecurrence(new Horde_Date('2011-12-01'));
440        $this->assertEquals('2011-12-10 15:00:00', (string)$next->setTimezone('America/New_York'));
441    }
442
443    public function testMissingSupportedTag()
444    {
445        $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base');
446        $fixture = array(
447            'userAgent' => 'Apple-iPad3C6/1202.435',
448            'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1')
449        );
450        $device = new Horde_ActiveSync_Device($state, $fixture);
451        $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN));
452        $contact->setSupported(array());
453        $this->assertEquals(false, $contact->isGhosted('subject'));
454        $this->assertEquals(false, $contact->isGhosted('body'));
455
456        $device = new Horde_ActiveSync_Device($state, $fixture);
457        $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_SIXTEEN));
458        $contact->setSupported(array());
459        $this->assertEquals(true, $contact->isGhosted('subject'));
460        $this->assertEquals(true, $contact->isGhosted('body'));
461    }
462
463    public function testEmptySupportedTag()
464    {
465        $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base');
466        $fixture = array(
467            'userAgent' => 'Apple-iPad3C6/1202.435',
468            'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1')
469        );
470        $device = new Horde_ActiveSync_Device($state, $fixture);
471        $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN));
472        $contact->setSupported(array(Horde_ActiveSync::ALL_GHOSTED));
473        $this->assertEquals(true, $contact->isGhosted('subject'));
474        $this->assertEquals(true, $contact->isGhosted('body'));
475
476        $device = new Horde_ActiveSync_Device($state, $fixture);
477        $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_SIXTEEN));
478        $contact->setSupported(array(Horde_ActiveSync::ALL_GHOSTED));
479        $this->assertEquals(true, $contact->isGhosted('subject'));
480        $this->assertEquals(true, $contact->isGhosted('body'));
481    }
482
483}
484