ics - encoder_test.go
1 package ics
2
3 import (
4 "bytes"
5 "net/url"
6 "reflect"
7 "strings"
8 "testing"
9 "time"
10 )
11
12 func TestEncode(t *testing.T) {
13 tzny, err := time.LoadLocation("America/New_York")
14 if err != nil {
15 t.Errorf("unexpected error: %s", err)
16 return
17 }
18 tests := []struct {
19 Input *Calendar
20 Output string
21 Error error
22 }{
23 {
24 Input: &Calendar{
25 ProductID: "TestDecode",
26 Version: "2.0",
27 },
28 Output: "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:TestDecode\r\nEND:VCALENDAR\r\n",
29 },
30 {
31 Input: &Calendar{
32 ProductID: "-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN",
33 Version: "2.0",
34 Event: []Event{
35 {
36 DateTimeStamp: PropDateTimeStamp{
37 Time: time.Date(1996, 7, 4, 12, 0, 0, 0, time.UTC),
38 },
39 UID: "uid1@example.com",
40 Organizer: &PropOrganizer{
41 CalendarAddress: CalendarAddress{
42 URL: url.URL{
43 Scheme: "mailto",
44 Opaque: "jsmith@example.com",
45 },
46 },
47 },
48 DateTimeStart: &PropDateTimeStart{
49 DateTime: &DateTime{
50 Time: time.Date(1996, 9, 18, 14, 30, 0, 0, time.UTC),
51 },
52 },
53 DateTimeEnd: &PropDateTimeEnd{
54 DateTime: &DateTime{
55 Time: time.Date(1996, 9, 20, 22, 0, 0, 0, time.UTC),
56 },
57 },
58 Status: StatusConfirmed.New(),
59 Categories: []PropCategories{
60 {
61 MText: MText{"CONFERENCE"},
62 },
63 },
64 Summary: &PropSummary{
65 Text: "Networld+Interop Conference",
66 },
67 Description: &PropDescription{
68 Text: "Networld+Interop Conference and Exhibit\nAtlanta World Congress Center\nAtlanta, Georgia",
69 },
70 },
71 },
72 },
73 Output: "BEGIN:VCALENDAR\r\n" +
74 "VERSION:2.0\r\n" +
75 "PRODID:-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN\r\n" +
76 "BEGIN:VEVENT\r\n" +
77 "DTSTAMP:19960704T120000Z\r\n" +
78 "UID:uid1@example.com\r\n" +
79 "DTSTART:19960918T143000Z\r\n" +
80 "DESCRIPTION:Networld+Interop Conference and Exhibit\\nAtlanta World Congress\r\n" +
81 " Center\\nAtlanta\\, Georgia\r\n" +
82 "ORGANIZER:mailto:jsmith@example.com\r\n" +
83 "STATUS:CONFIRMED\r\n" +
84 "SUMMARY:Networld+Interop Conference\r\n" +
85 "DTEND:19960920T220000Z\r\n" +
86 "CATEGORIES:CONFERENCE\r\n" +
87 "END:VEVENT\r\n" +
88 "END:VCALENDAR\r\n",
89 },
90 {
91 Input: &Calendar{
92 ProductID: "-//RDU Software//NONSGML HandCal//EN",
93 Version: "2.0",
94 Event: []Event{
95 {
96 DateTimeStamp: PropDateTimeStamp{
97 Time: time.Date(1998, 3, 9, 23, 10, 0, 0, time.UTC),
98 },
99 UID: "guid-1.example.com",
100 Organizer: &PropOrganizer{
101 CalendarAddress: CalendarAddress{
102 URL: url.URL{
103 Scheme: "mailto",
104 Opaque: "mrbig@example.com",
105 },
106 },
107 },
108 Attendee: []PropAttendee{
109 {
110 CalendarUserType: CalendarUserTypeGroup.New(),
111 RSVP: NewRSVP(true),
112 ParticipationRole: ParticipationRoleRequiredParticipant.New(),
113 CalendarAddress: CalendarAddress{
114 URL: url.URL{
115 Scheme: "mailto",
116 Opaque: "employee-A@example.com",
117 },
118 },
119 },
120 },
121 Description: &PropDescription{
122 Text: "Project XYZ Review Meeting",
123 },
124 Categories: []PropCategories{
125 {
126 MText: MText{"MEETING"},
127 },
128 },
129 Class: ClassPublic.New(),
130 Created: &PropCreated{
131 Time: time.Date(1998, 3, 9, 13, 0, 0, 0, time.UTC),
132 },
133 DateTimeStart: &PropDateTimeStart{
134 DateTime: &DateTime{
135 Time: time.Date(1998, 3, 12, 8, 30, 0, 0, tzny),
136 },
137 },
138 DateTimeEnd: &PropDateTimeEnd{
139 DateTime: &DateTime{
140 Time: time.Date(1998, 3, 12, 9, 30, 0, 0, tzny),
141 },
142 },
143 Location: &PropLocation{
144 Text: "1CP Conference Room 4350",
145 },
146 Summary: &PropSummary{
147 Text: "XYZ Project Review",
148 },
149 },
150 },
151 Timezone: []Timezone{
152 {
153 TimezoneID: "America/New_York",
154 Standard: []Standard{
155 {
156 DateTimeStart: PropDateTimeStart{
157 DateTime: &DateTime{
158 Time: time.Date(1998, 10, 25, 2, 0, 0, 0, time.Local),
159 },
160 },
161 TimezoneOffsetFrom: -4 * 3600,
162 TimezoneOffsetTo: -5 * 3600,
163 TimezoneName: []PropTimezoneName{
164 {
165 Text: "EST",
166 },
167 },
168 },
169 },
170 Daylight: []Daylight{
171 {
172 DateTimeStart: PropDateTimeStart{
173 DateTime: &DateTime{
174 Time: time.Date(1999, 4, 4, 2, 0, 0, 0, time.Local),
175 },
176 },
177 TimezoneOffsetFrom: -5 * 3600,
178 TimezoneOffsetTo: -4 * 3600,
179 TimezoneName: []PropTimezoneName{
180 {
181 Text: "EDT",
182 },
183 },
184 },
185 },
186 },
187 },
188 },
189 Output: "BEGIN:VCALENDAR\r\n" +
190 "VERSION:2.0\r\n" +
191 "PRODID:-//RDU Software//NONSGML HandCal//EN\r\n" +
192 "BEGIN:VEVENT\r\n" +
193 "DTSTAMP:19980309T231000Z\r\n" +
194 "UID:guid-1.example.com\r\n" +
195 "DTSTART;TZID=America/New_York:19980312T083000\r\n" +
196 "CLASS:PUBLIC\r\n" +
197 "CREATED:19980309T130000Z\r\n" +
198 "DESCRIPTION:Project XYZ Review Meeting\r\n" +
199 "LOCATION:1CP Conference Room 4350\r\n" +
200 "ORGANIZER:mailto:mrbig@example.com\r\n" +
201 "SUMMARY:XYZ Project Review\r\n" +
202 "DTEND;TZID=America/New_York:19980312T093000\r\n" +
203 "ATTENDEE;CUTYPE=GROUP;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:employee-A@exam\r\n" +
204 " ple.com\r\n" +
205 "CATEGORIES:MEETING\r\n" +
206 "END:VEVENT\r\n" +
207 "BEGIN:VTIMEZONE\r\n" +
208 "TZID:America/New_York\r\n" +
209 "BEGIN:STANDARD\r\n" +
210 "DTSTART:19981025T020000\r\n" +
211 "TZOFFSETTO:-0500\r\n" +
212 "TZOFFSETFROM:-0400\r\n" +
213 "TZNAME:EST\r\n" +
214 "END:STANDARD\r\n" +
215 "BEGIN:DAYLIGHT\r\n" +
216 "DTSTART:19990404T020000\r\n" +
217 "TZOFFSETTO:-0400\r\n" +
218 "TZOFFSETFROM:-0500\r\n" +
219 "TZNAME:EDT\r\n" +
220 "END:DAYLIGHT\r\n" +
221 "END:VTIMEZONE\r\n" +
222 "END:VCALENDAR\r\n",
223 },
224 {
225 Input: &Calendar{
226 Version: "2.0",
227 ProductID: "-//ABC Corporation//NONSGML My Product//EN",
228 Todo: []Todo{
229 {
230 DateTimeStamp: PropDateTimeStamp{
231 Time: time.Date(1998, 1, 30, 13, 45, 0, 0, time.UTC),
232 },
233 Sequence: NewSequence(2),
234 UID: "uid4@example.com",
235 Organizer: &PropOrganizer{
236 CalendarAddress: CalendarAddress{
237 URL: url.URL{
238 Scheme: "mailto",
239 Opaque: "unclesam@example.com",
240 },
241 },
242 },
243 Attendee: []PropAttendee{
244 {
245 ParticipationStatus: ParticipationStatusAccepted.New(),
246 CalendarAddress: CalendarAddress{
247 URL: url.URL{
248 Scheme: "mailto",
249 Opaque: "jqpublic@example.com",
250 },
251 },
252 },
253 },
254 Due: &PropDue{
255 DateTime: &DateTime{
256 Time: time.Date(1998, 4, 15, 0, 0, 0, 0, time.Local),
257 },
258 },
259 Status: StatusNeedsAction.New(),
260 Summary: &PropSummary{
261 Text: "Submit Income Taxes",
262 },
263 Alarm: []Alarm{
264 {
265 AlarmType: &AlarmAudio{
266 Trigger: PropTrigger{
267 DateTime: &DateTime{
268 Time: time.Date(1998, 4, 3, 12, 0, 0, 0, time.UTC),
269 },
270 },
271 Attachment: []PropAttachment{
272 {
273 FormatType: NewFormatType("audio/basic"),
274 URI: &URI{
275 URL: url.URL{
276 Scheme: "http",
277 Host: "example.com",
278 Path: "/pub/audio-files/ssbanner.aud",
279 },
280 },
281 },
282 },
283 Repeat: NewRepeat(4),
284 Duration: &PropDuration{
285 Hours: 1,
286 },
287 },
288 },
289 },
290 },
291 },
292 },
293 Output: "BEGIN:VCALENDAR\r\n" +
294 "VERSION:2.0\r\n" +
295 "PRODID:-//ABC Corporation//NONSGML My Product//EN\r\n" +
296 "BEGIN:VTODO\r\n" +
297 "DTSTAMP:19980130T134500Z\r\n" +
298 "UID:uid4@example.com\r\n" +
299 "ORGANIZER:mailto:unclesam@example.com\r\n" +
300 "SEQUENCE:2\r\n" +
301 "STATUS:NEEDS-ACTION\r\n" +
302 "SUMMARY:Submit Income Taxes\r\n" +
303 "DUE:19980415T000000\r\n" +
304 "ATTENDEE;PARTSTAT=ACCEPTED:mailto:jqpublic@example.com\r\n" +
305 "BEGIN:VALARM\r\n" +
306 "ACTION:AUDIO\r\n" +
307 "TRIGGER;VALUE=DATE-TIME:19980403T120000Z\r\n" +
308 "DURATION:PT1H\r\n" +
309 "REPEAT:4\r\n" +
310 "ATTACH;FMTTYPE=audio/basic:http://example.com/pub/audio-files/ssbanner.aud\r\n" +
311 "END:VALARM\r\n" +
312 "END:VTODO\r\n" +
313 "END:VCALENDAR\r\n",
314 },
315 {
316 Input: &Calendar{
317 Version: "2.0",
318 ProductID: "-//ABC Corporation//NONSGML My Product//EN",
319 Journal: []Journal{
320 {
321 DateTimeStamp: PropDateTimeStamp{
322 Time: time.Date(1997, 3, 24, 12, 0, 0, 0, time.UTC),
323 },
324 UID: "uid5@example.com",
325 Organizer: &PropOrganizer{
326 CalendarAddress: CalendarAddress{
327 URL: url.URL{
328 Scheme: "mailto",
329 Opaque: "jsmith@example.com",
330 },
331 },
332 },
333 Status: StatusDraft.New(),
334 Class: ClassPublic.New(),
335 Categories: []PropCategories{
336 {
337 MText: MText{
338 "Project Report",
339 "XYZ",
340 "Weekly Meeting",
341 },
342 },
343 },
344 Description: []PropDescription{
345 {
346 Text: "Project xyz Review Meeting Minutes\n" +
347 "Agenda\n" +
348 "1. Review of project version 1.0 requirements.\n" +
349 "2. Definition of project processes.\n" +
350 "3. Review of project schedule.\n" +
351 "Participants: John Smith, Jane Doe, Jim Dandy\n" +
352 "-It was decided that the requirements need to be signed off by product marketing.\n" +
353 "-Project processes were accepted.\n" +
354 "-Project schedule needs to account for scheduled holidays and employee vacation time. Check with HR for specific dates.\n" +
355 "-New schedule will be distributed by Friday.\n" +
356 "-Next weeks meeting is cancelled. No meeting until 3/23.",
357 },
358 },
359 },
360 },
361 },
362 Output: "BEGIN:VCALENDAR\r\n" +
363 "VERSION:2.0\r\n" +
364 "PRODID:-//ABC Corporation//NONSGML My Product//EN\r\n" +
365 "BEGIN:VJOURNAL\r\n" +
366 "DTSTAMP:19970324T120000Z\r\n" +
367 "UID:uid5@example.com\r\n" +
368 "CLASS:PUBLIC\r\n" +
369 "ORGANIZER:mailto:jsmith@example.com\r\n" +
370 "STATUS:DRAFT\r\n" +
371 "CATEGORIES:Project Report,XYZ,Weekly Meeting\r\n" +
372 "DESCRIPTION:Project xyz Review Meeting Minutes\\nAgenda\\n1. Review of projec\r\n" +
373 " t version 1.0 requirements.\\n2. Definition of project processes.\\n3. Revie\r\n" +
374 " w of project schedule.\\nParticipants: John Smith\\, Jane Doe\\, Jim Dandy\\n-\r\n" +
375 " It was decided that the requirements need to be signed off by product mark\r\n" +
376 " eting.\\n-Project processes were accepted.\\n-Project schedule needs to acco\r\n" +
377 " unt for scheduled holidays and employee vacation time. Check with HR for s\r\n" +
378 " pecific dates.\\n-New schedule will be distributed by Friday.\\n-Next weeks \r\n" +
379 " meeting is cancelled. No meeting until 3/23.\r\n" +
380 "END:VJOURNAL\r\n" +
381 "END:VCALENDAR\r\n",
382 },
383 {
384 Input: &Calendar{
385 Version: "2.0",
386 ProductID: "-//RDU Software//NONSGML HandCal//EN",
387 FreeBusy: []FreeBusy{
388 {
389 DateTimeStamp: PropDateTimeStamp{
390 Time: time.Date(1997, 3, 24, 12, 0, 0, 0, time.UTC),
391 },
392 UID: "uid5@example.com",
393 Organizer: &PropOrganizer{
394 CalendarAddress: CalendarAddress{
395 URL: url.URL{
396 Scheme: "mailto",
397 Opaque: "jsmith@example.com",
398 },
399 },
400 },
401 DateTimeStart: &PropDateTimeStart{
402 DateTime: &DateTime{
403 Time: time.Date(1998, 3, 13, 14, 17, 11, 0, time.UTC),
404 },
405 },
406 DateTimeEnd: &PropDateTimeEnd{
407 DateTime: &DateTime{
408 Time: time.Date(1998, 4, 10, 14, 17, 11, 0, time.UTC),
409 },
410 },
411 FreeBusy: []PropFreeBusy{
412 {
413 Period: Period{
414 Start: DateTime{
415 Time: time.Date(1998, 3, 14, 23, 30, 0, 0, time.UTC),
416 },
417 End: DateTime{
418 Time: time.Date(1998, 3, 15, 0, 30, 0, 0, time.UTC),
419 },
420 },
421 },
422 {
423 Period: Period{
424 Start: DateTime{
425 Time: time.Date(1998, 3, 16, 15, 30, 0, 0, time.UTC),
426 },
427 End: DateTime{
428 Time: time.Date(1998, 3, 16, 16, 30, 0, 0, time.UTC),
429 },
430 },
431 },
432 {
433 Period: Period{
434 Start: DateTime{
435 Time: time.Date(1998, 3, 18, 3, 0, 0, 0, time.UTC),
436 },
437 End: DateTime{
438 Time: time.Date(1998, 3, 18, 4, 0, 0, 0, time.UTC),
439 },
440 },
441 },
442 },
443 URL: &PropURL{
444 URL: url.URL{
445 Scheme: "http",
446 Host: "www.example.com",
447 Path: "/calendar/busytime/jsmith.ifb",
448 },
449 },
450 },
451 },
452 },
453 Output: "BEGIN:VCALENDAR\r\n" +
454 "VERSION:2.0\r\n" +
455 "PRODID:-//RDU Software//NONSGML HandCal//EN\r\n" +
456 "BEGIN:VFREEBUSY\r\n" +
457 "DTSTAMP:19970324T120000Z\r\n" +
458 "UID:uid5@example.com\r\n" +
459 "DTSTART:19980313T141711Z\r\n" +
460 "DTEND:19980410T141711Z\r\n" +
461 "ORGANIZER:mailto:jsmith@example.com\r\n" +
462 "URL:http://www.example.com/calendar/busytime/jsmith.ifb\r\n" +
463 "FREEBUSY:19980314T233000Z/19980315T003000Z\r\n" +
464 "FREEBUSY:19980316T153000Z/19980316T163000Z\r\n" +
465 "FREEBUSY:19980318T030000Z/19980318T040000Z\r\n" +
466 "END:VFREEBUSY\r\n" +
467 "END:VCALENDAR\r\n",
468 },
469 }
470
471 var buf bytes.Buffer
472 for n, test := range tests {
473 err := Encode(&buf, test.Input)
474 if err != test.Error {
475 t.Errorf("test %d: expecting error %q, got %q", n+1, test.Error, err)
476 } else if str := buf.String(); str != test.Output {
477 t.Errorf("test %d: expecting calendar %s, got %s", n+1, test.Output, str)
478 } else {
479 c, err := Decode(strings.NewReader(str))
480 if err != nil {
481 t.Errorf("test %d: unexpected error decoding encoded string: %s", n+1, err)
482 } else if !reflect.DeepEqual(c, test.Input) {
483 t.Errorf("test %d: expecting calendar %s, got %s", n+1, test.Input, c)
484 }
485 }
486 buf.Reset()
487 }
488 }