// @format
const test = require("ava");
const createWorker = require("expressively-mocked-fetch");
const add = require("date-fns/add");
const sub = require("date-fns/sub");
const moment = require("moment");
const { Time, Duration } = require("ical.js");
const {
SimpleCalDAV,
errors: { ParserError, ServerError, InputError }
} = require("../src/index.js");
test("if parameters are correctly stored", t => {
const URI = "https://example.com";
const dav = new SimpleCalDAV(URI);
t.assert(dav.uri === URI);
});
test("if objects are correctly exported", t => {
const libObj = require("../src/index.js");
t.assert("errors" in libObj);
t.assert("SimpleCalDAV" in libObj);
t.assert("ParserError" in libObj.errors);
t.assert("ServerError" in libObj.errors);
t.assert("InputError" in libObj.errors);
});
test("test fetching empty calendar", async t => {
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/50113370-f61f-4444-9e94-e3ba1d2467b8.ics
"aa98130e9fac911f70a73dac8b57e58a482b04ec4b8a5417dfedf8f42069c6d0"
BEGIN:VCALENDAR
VERSION:2.0
PRODID: blaaa
END:VCALENDAR
HTTP/1.1 200 OK
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
await t.throwsAsync(
async () => {
await dav.listEvents();
},
{ instanceOf: ParserError }
);
});
test("fetching ics-incompatible response", async t => {
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
hello world
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const events = await dav.listEvents();
t.assert(events.length === 0);
});
test("fetching calendar single event without an alarm", async t => {
const summary = "Work on this lib";
const description = "description";
const time = "20200729T130856Z";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const href =
"/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/50113370-f61f-4444-9e94-e3ba1d2467b8.ics";
const organizer = {
commonName: "John Smith",
email: "john@smith.de"
};
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
${href}
"aa98130e9fac911f70a73dac8b57e58a482b04ec4b8a5417dfedf8f42069c6d0"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:50113370-f61f-4444-9e94-e3ba1d2467b8
DTSTART;TZID=Europe/Berlin:20200717T100000
DTEND;TZID=Europe/Berlin:20200717T133000
CREATED:20200717T143449Z
DTSTAMP:20200717T143454Z
LAST-MODIFIED:20200717T143454Z
STATUS:TENTATIVE
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
SUMMARY:${summary}
LOCATION:${_location}
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const events = await dav.listEvents();
t.assert(events.length === 1);
t.assert(events[0].summary.value === summary);
t.assert(events[0].start instanceof Date);
t.assert(events[0].end instanceof Date);
t.assert(events[0].alarms.length === 0);
t.assert(events[0]._status === "TENTATIVE");
t.assert(events[0]._location.value === _location);
t.assert(events[0].href === URI + href);
t.deepEqual(events[0].organizer, organizer);
});
test("fetching calendar single event without an alarm and without a status and a location", async t => {
const summary = "Work on this lib";
const description = "description";
const time = "20200729T130856Z";
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/50113370-f61f-4444-9e94-e3ba1d2467b8.ics
"aa98130e9fac911f70a73dac8b57e58a482b04ec4b8a5417dfedf8f42069c6d0"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:50113370-f61f-4444-9e94-e3ba1d2467b8
DTSTART;TZID=Europe/Berlin:20200717T100000
DTEND;TZID=Europe/Berlin:20200717T133000
CREATED:20200717T143449Z
DTSTAMP:20200717T143454Z
LAST-MODIFIED:20200717T143454Z
SUMMARY:${summary}
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const events = await dav.listEvents();
t.assert(events.length === 1);
t.assert(events[0].summary.value === summary);
t.assert(events[0].start instanceof Date);
t.assert(events[0].end instanceof Date);
t.assert(events[0].alarms.length === 0);
t.assert("_status" in events[0]);
t.assert(events[0]._status === null);
});
test("fetching calendar single event with a relative alarm trigger", async t => {
const summary = "Work on this lib";
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/50113370-f61f-4444-9e94-e3ba1d2467b8.ics
"aa98130e9fac911f70a73dac8b57e58a482b04ec4b8a5417dfedf8f42069c6d0"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:50113370-f61f-4444-9e94-e3ba1d2467b8
DTSTART;TZID=Europe/Berlin:20200717T100000
DTEND;TZID=Europe/Berlin:20200717T133000
CREATED:20200717T143449Z
DTSTAMP:20200717T143454Z
LAST-MODIFIED:20200717T143454Z
SUMMARY:${summary}
STATUS:CONFIRMED
TRANSP:OPAQUE
X-MOZ-GENERATION:1
BEGIN:VALARM
ACTION:EMAIL
DESCRIPTION:Email reminder with relative trigger
ATTENDEE:mailto:${attendee}
TRIGGER:-PT15M
END:VALARM
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
\`);
});
`);
//TRIGGER;VALUE=DATE-TIME:20200729T140856Z
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const events = await dav.listEvents();
t.assert(events.length === 1);
t.assert(events[0].summary.value === summary);
t.assert(events[0].start instanceof Date);
t.assert(events[0].end instanceof Date);
t.assert(events[0].alarms.length === 1);
t.assert(events[0]._status === "CONFIRMED");
});
test("fetching calendar single event", async t => {
const summary = "Work on this lib";
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const organizer = {
commonName: "John Smith",
email: "john@smith.com"
};
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/50113370-f61f-4444-9e94-e3ba1d2467b8.ics
"aa98130e9fac911f70a73dac8b57e58a482b04ec4b8a5417dfedf8f42069c6d0"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:50113370-f61f-4444-9e94-e3ba1d2467b8
DTSTART;TZID=Europe/Berlin:20200717T100000
DTEND;TZID=Europe/Berlin:20200717T133000
CREATED:20200717T143449Z
DTSTAMP:20200717T143454Z
LAST-MODIFIED:20200717T143454Z
SUMMARY:${summary}
TRANSP:OPAQUE
X-MOZ-GENERATION:1
STATUS:CONFIRMED
LOCATION:${_location}
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
BEGIN:VALARM
ACTION:EMAIL
ATTENDEE:mailto:me@example.com
SUMMARY:${summary}
DESCRIPTION:A email body
TRIGGER;VALUE=DATE-TIME:20200729T140856Z
END:VALARM
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const events = await dav.listEvents();
t.assert(events.length === 1);
t.assert(events[0].summary.value === summary);
t.assert(events[0].start instanceof Date);
t.assert(events[0].end instanceof Date);
t.assert(events[0]._status === "CONFIRMED");
t.deepEqual(events[0].organizer, organizer);
t.assert((events[0]._location.value = _location));
t.assert(events[0].alarms.length === 2);
t.assert(events[0].alarms[0].action === action);
t.assert(events[0].alarms[0].attendee === attendee);
t.assert(events[0].alarms[0].trigger instanceof Date);
t.assert(events[0].alarms[1].summary.value === summary);
});
test("fetching calendar with multiple events", async t => {
const summary = "Work on this lib";
const worker = await createWorker(`
app.report('/', function (req, res) {
res.send(\`
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/50113370-f61f-4444-9e94-e3ba1d2467b8.ics
"aa98130e9fac911f70a73dac8b57e58a482b04ec4b8a5417dfedf8f42069c6d0"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
STATUS:TENTATIVE
UID:50113370-f61f-4444-9e94-e3ba1d2467b8
DTSTART;TZID=Europe/Berlin:20200717T100000
DTEND;TZID=Europe/Berlin:20200717T133000
CREATED:20200717T143449Z
DTSTAMP:20200717T143454Z
LAST-MODIFIED:20200717T143454Z
SUMMARY:${summary}
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/105b112e-7d65-3147-a182-deaf17d08a12.ics
"86b95c0081a021570746219276242ba6fb5b59632260d3ef1740d37c2ce806f2"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
STATUS:CONFIRMED
UID:105b112e-7d65-3147-a182-deaf17d08a12
DTSTART;TZID=Europe/Berlin:20200718T094500
DTEND;TZID=Europe/Berlin:20200718T131500
CREATED:20200717T143444Z
DTSTAMP:20200717T143446Z
LAST-MODIFIED:20200717T143446Z
SUMMARY:Biketour
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const events = await dav.listEvents();
t.assert(events.length === 2);
t.assert(events[0].summary.value === summary);
t.assert(events[0].start instanceof Date);
t.assert(events[0].end instanceof Date);
t.assert(events[0]._status === "TENTATIVE");
t.assert(events[1]._status === "CONFIRMED");
});
test("formatting a date to iCal compliant date time", t => {
// NOTE: For reference, see https://tools.ietf.org/html/rfc5545 under:
// "FORM #2: DATE WITH UTC TIME"
const expected = new Date();
const formatted = SimpleCalDAV.formatDateTime(expected);
const format = new RegExp(
"[0-9]{4}[0-1][0-9][0-3][0-9]T[0-2][0-9][0-6][0-9]\\d{2}Z"
);
t.assert(format.test(formatted));
// NOTE: We import moment here as a dev dependency as it was originally used
// by simple-caldav to create the iCAL UTC time stamp
const converted = moment(expected)
.utc()
.format("YMMDDTHHmmss[Z]");
t.assert(converted === formatted);
});
test("creating an event", async t => {
const worker = await createWorker(`
app.put('/:resource', function (req, res) {
res.status(201).send();
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const start = new Date();
const end = add(new Date(), { hours: 1 });
const res = await dav.createEvent(start, end, "test summary");
t.assert(res.status === 201);
});
test("creating an event with alarm", async t => {
const worker = await createWorker(`
app.put('/:resource', function (req, res) {
if (req.body.includes(":mailto:mailto:")) {
return res.status(500).send();
}
res.status(201).send();
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const start = new Date();
const end = add(new Date(), { hours: 1 });
const alarms = [
{
action: "email",
summary: "Email's summary",
description: "email's description",
trigger: new Date(),
attendee: "email@example.com"
},
{
action: "email",
summary: "Email's summary",
description: "email's description",
trigger: add(new Date(), { hours: 1 }),
attendee: "email@example.com"
}
];
const res = await dav.createEvent(start, end, "test summary", alarms);
t.assert(res.status === 201);
});
test("updating an event completely", async t => {
const uid = "6720d455-76aa-4740-8766-c064df95bb3b";
const worker = await createWorker(`
app.put('/${uid}.ics', function (req, res) {
res.status(201).send();
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const start = new Date();
const end = add(new Date(), { hours: 1 });
const res = await dav.updateEvent(uid, start, end, "updated summary");
t.assert(res.status === 201);
});
test("transforming an event without alarms to a VEVENT", t => {
const evt = {
start: new Date(),
end: new Date(),
summary: {
value: "abc"
},
uid: "uid",
_status: "CONFIRMED",
_location: {
value: "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte"
},
organizer: {
commonName: "John Smith",
email: "john@smith.com"
}
};
const vevent = SimpleCalDAV.toVEVENT(evt);
t.assert(new RegExp("UID:uid\\n").test(vevent));
t.assert(new RegExp("SUMMARY:abc\\n").test(vevent));
t.assert(new RegExp("DTSTART:\\d{8}T\\d{6}Z\\n").test(vevent));
t.assert(new RegExp("DTEND:\\d{8}T\\d{6}Z\\n").test(vevent));
t.assert(new RegExp("DTSTAMP:\\d{8}T\\d{6}Z\\n").test(vevent));
t.assert(new RegExp("STATUS:CONFIRMED\\n").test(vevent));
t.assert(new RegExp(`LOCATION:${evt._location.value}\\n`).test(vevent));
t.assert(
new RegExp(
`ORGANIZER;CN=${evt.organizer.commonName}:mailto:${
evt.organizer.email
}\\n`
).test(vevent)
);
});
test("transforming an event with an organizer and no common name", t => {
const evt = {
start: new Date(),
end: new Date(),
summary: "abc",
uid: "uid",
_status: "CONFIRMED",
organizer: {
email: "john@smith.com"
}
};
const vevent = SimpleCalDAV.toVEVENT(evt);
t.assert(
new RegExp(`ORGANIZER:mailto:${evt.organizer.email}\\n`).test(vevent)
);
});
test("transforming an email alarm into a VALARM", t => {
const alarm = {
action: "email",
summary: {
value: "Email's summary"
},
description: { value: "email's description" },
trigger: new Date(),
attendee: "email@example.com"
};
const valarm = SimpleCalDAV.toVALARM(alarm);
t.assert(new RegExp("ACTION:EMAIL").test(valarm));
t.assert(new RegExp(`SUMMARY:${alarm.summary.value}`).test(valarm));
t.assert(new RegExp(`DESCRIPTION:${alarm.description.value}`).test(valarm));
t.assert(new RegExp("TRIGGER;VALUE=DATE-TIME:\\d{8}T\\d{6}Z").test(valarm));
t.assert(new RegExp(`ATTENDEE:mailto:${alarm.attendee}`).test(valarm));
});
test("transforming an email alarm with a relative trigger into a VALARM", t => {
const alarm = {
action: "email",
summary: {
value: "Email's summary"
},
description: {
value: "email's description"
},
trigger: {
minutes: 15,
isNegative: true
},
attendee: "email@example.com"
};
const valarm = SimpleCalDAV.toVALARM(alarm);
t.assert(new RegExp("ACTION:EMAIL").test(valarm));
t.assert(new RegExp(`SUMMARY:${alarm.summary.value}`).test(valarm));
t.assert(new RegExp(`DESCRIPTION:${alarm.description.value}`).test(valarm));
t.assert(new RegExp(`TRIGGER:-PT${alarm.trigger.minutes}M`).test(valarm));
t.assert(new RegExp(`ATTENDEE:mailto:${alarm.attendee}`).test(valarm));
});
test("transforming an object to a negative relative trigger", t => {
const trigger = {
minutes: 15,
isNegative: true
};
const actual = SimpleCalDAV.toTrigger(trigger);
t.assert(new RegExp(`TRIGGER:-PT${trigger.minutes}M`).test(actual));
});
test("transforming an object to a positive relative trigger", t => {
const trigger = {
minutes: 15,
isNegative: false
};
const actual = SimpleCalDAV.toTrigger(trigger);
t.assert(new RegExp(`TRIGGER:PT${trigger.minutes}M`).test(actual));
});
test("if transforming into a trigger fails if input is incorrect", t => {
const wrong = {
hello: "world"
};
t.throws(() => SimpleCalDAV.toTrigger(wrong), { instanceOf: InputError });
});
test("transforming date to absolute trigger", t => {
const actual = SimpleCalDAV.toTrigger(new Date());
t.assert(new RegExp("TRIGGER;VALUE=DATE-TIME:\\d{8}T\\d{6}Z").test(actual));
});
test("transforming an sms alarm into a VALARM", t => {
const alarm = {
action: "sms",
description: {
value: "sms's description"
},
trigger: new Date(),
attendee: "0123456789"
};
const valarm = SimpleCalDAV.toVALARM(alarm);
t.assert(new RegExp("ACTION:SMS\\n").test(valarm));
t.assert(
new RegExp(`DESCRIPTION:${alarm.description.value}\\n`).test(valarm)
);
t.assert(
new RegExp("TRIGGER;VALUE=DATE-TIME:\\d{8}T\\d{6}Z\\n").test(valarm)
);
t.assert(new RegExp(`ATTENDEE:sms:${alarm.attendee}\\n`).test(valarm));
});
test("getting sync token", async t => {
const syncToken = "abc";
const displayName = "displayname";
const worker = await createWorker(`
app.propfind('/', function (req, res) {
res.status(201).send(\`
/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/
${displayName}
"09aad437ed2e4b4cd8d700ad410385d9b13e9fd964862d7f2987e4c844237465"
${syncToken}
HTTP/1.1 200 OK
\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const token = await dav.getSyncToken();
t.assert(token.syncToken === syncToken);
t.assert(token.displayName === displayName);
});
test("getting collection with a sync token", async t => {
const href = "https://example.com";
const etag = "etag";
const status = "HTTP/1.1 200";
const syncToken1 = "1";
const syncToken2 = "2";
// TODO: Also implement test for 404 asset
const worker = await createWorker(
`
let counter = 0;
app.report('/', function (req, res) {
if (counter === 0) {
res.status(201).send(\`
${syncToken1}
${href}
${etag}
${status}
\`);
} else if (counter === 1) {
res.status(201).send(\`
${syncToken2}
\`);
}
counter++;
});
`,
2
);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const col = await dav.syncCollection();
t.assert(col.syncToken === syncToken1);
t.assert(col.collection.length === 1);
t.assert(col.collection[0].href === href);
t.assert(col.collection[0].etag === etag);
t.assert(col.collection[0].statusCode === 200);
const emptyCol = await dav.syncCollection(col.syncToken);
t.assert(emptyCol.syncToken === syncToken2);
t.assert(emptyCol.collection.length === 0);
});
test("getting a single event", async t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const summary = "bla";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const uid = "abc";
const organizer = {
email: "john@smith.com",
commonName: "John Smith"
};
const worker = await createWorker(`
app.get('/:uid', function (req, res) {
res.status(201).send(\`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY:new one
LOCATION:${_location}
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
BEGIN:VALARM
ACTION:EMAIL
ATTENDEE:mailto:me@example.com
SUMMARY:${summary}
DESCRIPTION:A email body
TRIGGER;VALUE=DATE-TIME:20200729T140856Z
END:VALARM
END:VEVENT
END:VCALENDAR\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const evt = await dav.getEvent(uid);
t.assert("summary" in evt);
t.assert("start" in evt);
t.assert("end" in evt);
t.assert("alarms" in evt);
t.assert("_status" in evt);
t.assert("organizer" in evt);
t.assert("_location" in evt);
t.assert(evt._location.value === _location);
t.deepEqual(evt.organizer, organizer);
t.assert(evt.href === `${URI}/${uid}.ics`);
t.assert(evt.alarms.length === 2);
t.assert(evt.alarms[0].action === action);
t.assert(evt.alarms[0].attendee === attendee);
t.assert(evt.alarms[0].trigger instanceof Date);
t.assert(evt.alarms[1].summary.value === summary);
});
test("getting a single event but without any organizer present", async t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const summary = "bla";
const uid = "6720d455-76aa-4740-8766-c064df95bb3b";
const worker = await createWorker(`
app.get('/:uid', function (req, res) {
res.status(201).send(\`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY:new one
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
BEGIN:VALARM
ACTION:EMAIL
ATTENDEE:mailto:me@example.com
SUMMARY:${summary}
DESCRIPTION:A email body
TRIGGER;VALUE=DATE-TIME:20200729T140856Z
END:VALARM
END:VEVENT
END:VCALENDAR\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const evt = await dav.getEvent(uid);
t.assert("summary" in evt);
t.assert("start" in evt);
t.assert("end" in evt);
t.assert("alarms" in evt);
t.assert("_status" in evt);
t.assert(!("organizer" in evt));
t.assert(evt.href === `${URI}/${uid}.ics`);
t.assert(evt.alarms.length === 2);
t.assert(evt.alarms[0].action === action);
t.assert(evt.alarms[0].attendee === attendee);
t.assert(evt.alarms[0].trigger instanceof Date);
t.assert(evt.alarms[1].summary.value === summary);
});
test("getting a single event but only with organizer email present", async t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const summary = "bla";
const organizer = { email: "john@smith.com" };
const uid = "6720d455-76aa-4740-8766-c064df95bb3b";
const worker = await createWorker(`
app.get('/:uid', function (req, res) {
res.status(201).send(\`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY:new one
ORGANIZER:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
BEGIN:VALARM
ACTION:EMAIL
ATTENDEE:mailto:me@example.com
SUMMARY:${summary}
DESCRIPTION:A email body
TRIGGER;VALUE=DATE-TIME:20200729T140856Z
END:VALARM
END:VEVENT
END:VCALENDAR\`);
});
`);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const evt = await dav.getEvent(uid);
t.assert("summary" in evt);
t.assert("start" in evt);
t.assert("end" in evt);
t.assert("alarms" in evt);
t.assert("_status" in evt);
t.assert("organizer" in evt);
t.assert(evt.href === `${URI}/${uid}.ics`);
t.deepEqual(evt.organizer, organizer);
t.assert(evt.alarms.length === 2);
t.assert(evt.alarms[0].action === action);
t.assert(evt.alarms[0].attendee === attendee);
t.assert(evt.alarms[0].trigger instanceof Date);
t.assert(evt.alarms[1].summary.value === summary);
});
test("getting a single event, but server returns html which is valid xml but not valid response", async t => {
const worker = await createWorker(`
app.get('/:uid', function (req, res) {
res.status(200).send("");
});
`);
//const URI = `http://example.com/event.ics`;
const URI =
"https://cloud1.daubenschuetz.de /radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/";
const dav = new SimpleCalDAV(URI);
await t.throwsAsync(
async () => {
await dav.getEvent("event");
},
{ instanceOf: ParserError }
);
});
test("if syncCollection returns collection with correctly ordered properties", async t => {
const href = "1";
const href2 = "2";
const etag = "etag";
const worker = await createWorker(
`
app.report('/', function (req, res) {
res.status(201).send(\`
1
${href}
HTTP/1.1 404 Not Found
${href2}
${etag}
HTTP/1.1 200 OK
\`);
});
`
);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const col = await dav.syncCollection();
t.assert(col.collection[0].statusCode === 404);
t.assert(col.collection[0].href === href);
t.assert(!col.collection[0].etag);
t.assert(col.collection[1].statusCode === 200);
t.assert(col.collection[1].href === href2);
t.assert(col.collection[1].etag === etag);
});
test("if single deletion response is detected by parser", async t => {
const worker = await createWorker(
`
app.report('/', function (req, res) {
res.status(201).send(\`
1
a
HTTP/1.1 404 Not Found
\`);
});
`
);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const { collection } = await dav.syncCollection();
t.assert(collection.length == 1);
t.assert(collection[0].href === "a");
t.assert(collection[0].statusCode === 404);
});
test("submitting incorrect status when transforming to VEVENT", t => {
const evt = {
start: new Date(),
end: new Date(),
summary: "abc",
uid: "uid",
_status: "LOLNOTALLOWED"
};
t.throws(() => SimpleCalDAV.toVEVENT(evt), { instanceOf: ParserError });
});
test("extracting uid from href", t => {
const expected = "6720d455-76aa-4740-8766-c064df95bb3b";
const href = `/radicale/example%40gmail.com/8409b6d2-8dcc-997b-45d6-517801237d38/${expected}.ics`;
const uid = SimpleCalDAV.extractUid(href);
t.assert(uid === expected);
});
test("if two alarms cause a bug where a comma shows up in iCAL result", async t => {
const worker = await createWorker(`
app.put('/:resource', function (req, res) {
if (req.body.includes(",BEGIN:VALARM")) {
res.status(500).send();
} else {
res.status(201).send();
}
});
`);
const URI = `http://localhost:${worker.port}`;
const start = new Date();
const end = add(new Date(), { hours: 1 });
const alarms = [
{
action: "email",
summary: "Email's summary",
description: "email's description",
trigger: new Date(),
attendee: "email@example.com"
},
{
action: "email",
summary: "Email's summary",
description: "email's description",
trigger: add(new Date(), { hours: 1 }),
attendee: "email@example.com"
}
];
const dav = new SimpleCalDAV(URI);
const res = await dav.createEvent(
start,
end,
"test summary",
alarms,
"CONFIRMED"
);
t.assert(res.status === 201);
});
test("updating an event to check whether mailto: and sms: multiply", async t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const summary = "bla";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const uid = "abc";
const phone = "+491795345170";
const organizer = {
email: "john@smith.com",
commonName: "John Smith"
};
const worker = await createWorker(
`
app.get('/:uid', function (req, res) {
res.status(201).send(\`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY:new one
LOCATION:${_location}
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
BEGIN:VALARM
ACTION:SMS
ATTENDEE:sms:sms:${phone}
DESCRIPTION:Irgendein alarm
SUMMARY:${summary}
TRIGGER;VALUE=DATE-TIME:20201003T123000Z
END:VALARM
BEGIN:VALARM
ACTION:SMS
ATTENDEE:sms:${phone}
DESCRIPTION:Irgendein alarm
SUMMARY:${summary}
TRIGGER;VALUE=DATE-TIME:20201003T123000Z
END:VALARM
END:VEVENT
END:VCALENDAR\`);
});
app.put('/${uid}.ics', function (req, res) {
if (req.body.includes(":mailto:mailto:") || req.body.includes(":sms:sms:")) {
return res.status(500).send();
}
res.status(201).send();
});
`,
2
);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const evt = await dav.getEvent(uid);
t.assert("summary" in evt);
t.assert("start" in evt);
t.assert("end" in evt);
t.assert("alarms" in evt);
t.assert("_status" in evt);
t.assert("organizer" in evt);
t.assert("_location" in evt);
t.assert(evt._location.value === _location);
t.deepEqual(evt.organizer, organizer);
t.assert(evt.href === `${URI}/${uid}.ics`);
t.assert(evt.alarms.length === 4);
t.assert(evt.alarms[0].action === action);
t.assert(evt.alarms[0].attendee === attendee);
t.assert(evt.alarms[0].trigger instanceof Date);
t.assert(evt.alarms[1].attendee === attendee);
t.assert(evt.alarms[2].summary.value === summary);
t.assert(evt.alarms[3].attendee === phone);
const res = await dav.updateEvent(
uid,
evt.start,
evt.end,
"updated summary",
evt.alarms,
"CONFIRMED"
);
t.assert(res.status === 201);
});
test("if language params that are not the default are kept when updating an event", async t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const language = "de-DE";
const summary = "bla";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const uid = "abc";
const phone = "+491795345170";
const organizer = {
email: "john@smith.com",
commonName: "John Smith"
};
const worker = await createWorker(
`
app.get('/:uid', function (req, res) {
res.status(201).send(\`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY;LANGUAGE=${language}:new one
LOCATION:${_location}
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION;LANGUAGE=${language}:${description}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
END:VEVENT
END:VCALENDAR\`);
});
app.put('/${uid}.ics', function (req, res) {
if (req.body.includes(";LANGUAGE=${language}")) {
return res.status(201).send();
}
res.status(500).send();
});
`,
2
);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI);
const evt = await dav.getEvent(uid);
t.assert("summary" in evt);
t.assert("start" in evt);
t.assert("end" in evt);
t.assert("alarms" in evt);
t.assert("_status" in evt);
t.assert("organizer" in evt);
t.assert("_location" in evt);
t.assert(evt._location.value === _location);
t.deepEqual(evt.organizer, organizer);
t.assert(evt.href === `${URI}/${uid}.ics`);
t.assert(evt.alarms.length === 1);
t.assert(evt.alarms[0].action === action);
t.assert(evt.alarms[0].attendee === attendee);
t.assert(evt.alarms[0].trigger instanceof Date);
const res = await dav.updateEvent(
uid,
evt.start,
evt.end,
"updated summary",
evt.alarms,
"CONFIRMED"
);
t.assert(res.status === 201);
});
test("if absolute trigger is parsed correctly", t => {
const dateObj = {
year: 2012,
month: 10,
day: 11,
hour: 15,
minute: 0,
second: 0,
isDate: false
};
const time = new Time(dateObj);
const actual = SimpleCalDAV.parseTrigger(time);
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const actualLocalized = actual.toLocaleString("en-US", {
timeZone,
hour12: false
});
const expected = `${dateObj.month}/${dateObj.day}/${dateObj.year} ${
dateObj.hour
}:${dateObj.minute}:${dateObj.second}`;
t.assert(expected, actualLocalized);
});
test("if positive relative trigger (duration) is parsed correctly", t => {
const durObj = {
minutes: 123,
isNegative: false
};
const dur = new Duration(durObj);
const actual = SimpleCalDAV.parseTrigger(dur);
t.deepEqual({ ...durObj, weeks: 0, days: 0, hours: 0, seconds: 0 }, actual);
});
test("if negative relative trigger (duration) is parsed correctly", t => {
const durObj = {
minutes: 123,
isNegative: true
};
const dur = new Duration(durObj);
const actual = SimpleCalDAV.parseTrigger(dur);
t.deepEqual({ ...durObj, weeks: 0, days: 0, hours: 0, seconds: 0 }, actual);
});
test("if error is thrown when incorrect object is attempted to be parsed", t => {
class Wrong {
constructor() {}
}
t.throws(
() => {
SimpleCalDAV.parseTrigger(new Wrong());
},
{ instanceOf: InputError }
);
});
test("if negative duration yields negative seconds value", t => {
const durObj = {
minutes: 1,
isNegative: true
};
const dur = new Duration(durObj);
t.assert(dur.toSeconds() === -60);
});
test("if date-fns add can handle negative values", t => {
const now = new Date();
t.assert(
add(now, { seconds: -1 }).toISOString() ===
sub(now, { seconds: 1 }).toISOString()
);
});
test("if adding a negative duration is same as subtracting a positive duration", t => {
const now = new Date();
const durObj = {
minutes: 1,
isNegative: true
};
const dur = new Duration(durObj);
const durObjPos = {
minutes: 1,
isNegative: false
};
const durPos = new Duration(durObjPos);
t.assert(
add(now, { seconds: dur.toSeconds() }).toISOString() ===
sub(now, { seconds: durPos.toSeconds() }).toISOString()
);
});
test("if durations are applied to a date correctly", t => {
const now = new Date();
const durObj = {
minutes: 1,
isNegative: true
};
const dur = new Duration(durObj);
t.assert(
add(now, { seconds: dur.toSeconds() }).toISOString() ===
SimpleCalDAV.applyDuration(now, durObj).toISOString()
);
});
test("if error is thrown when wrong parameters are submitted when applying duration", t => {
t.throws(() => SimpleCalDAV.applyDuration("this is wrong"), {
instanceOf: InputError
});
t.throws(() => SimpleCalDAV.applyDuration(new Date(), null), {
instanceOf: InputError
});
t.throws(() => SimpleCalDAV.applyDuration(new Date(), { minutes: 1 }), {
instanceOf: InputError
});
t.throws(
() => SimpleCalDAV.applyDuration(new Date(), { isNegative: false }),
{
instanceOf: InputError
}
);
t.throws(
() =>
SimpleCalDAV.applyDuration(new Date(), {
otherthings: "abc",
isNegative: true
}),
{
instanceOf: InputError
}
);
});
test("if xmldoc can serve simple-caldav's use cases", t => {
const xmldoc = require("xmldoc");
const href1 = "href1";
const href2 = "href2";
const content1 = "content1";
const content2 = "content2";
const s = `
${href1}
${content1}
HTTP/1.1 200 OK
${href2}
${content2}
HTTP/1.1 200 OK
`;
const doc = new xmldoc.XmlDocument(s);
t.assert(
doc.childrenNamed("response")[1].descendantWithPath("href").val === href2
);
t.assert(doc.descendantWithPath("response.href").val === href1);
});
test("if options are included in fetch requests", async t => {
const worker = await createWorker(
`
const fn = function (req, res) {
if (req.get("X-TEST") === "test") {
res.status(200).send();
} else {
res.status(500).send();
}
};
app.put("/:uid", fn);
`,
2
);
const URI = `http://localhost:${worker.port}`;
const dav = new SimpleCalDAV(URI, { headers: { "X-TEST": "test" } });
const start = new Date();
const end = add(new Date(), { hours: 1 });
const res = await dav.createEvent(start, end);
t.assert(res.status === 200);
});
test("if simplify event can handle language property on tags like summary, description, etc.", t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const language = "de-DE";
const summary = "a new summary";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const uid = "abc";
const phone = "+491795345170";
const organizer = {
email: "john@smith.com",
commonName: "John Smith"
};
const vevent = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY;LANGUAGE=${language}:${summary}
LOCATION;LANGUAGE=${language}:${_location}
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION;LANGUAGE=${language}:${description}
TRIGGER;VALUE=DATE-TIME:${time}
SUMMARY;LANGUAGE=${language}:${summary}
END:VALARM
END:VEVENT
END:VCALENDAR`;
let evt = SimpleCalDAV.parseICS(vevent);
evt = SimpleCalDAV.simplifyEvent(evt, "uri");
t.truthy(evt.summary);
t.is(evt.summary.language, language);
t.is(evt.summary.value, summary);
t.truthy(evt._location);
t.is(evt._location.language, language);
t.is(evt._location.value, _location);
t.truthy(evt.alarms[0].summary);
t.is(evt.alarms[0].summary.value, summary);
t.is(evt.alarms[0].summary.language, language);
t.truthy(evt.alarms[0].description);
t.is(evt.alarms[0].description.value, description);
t.is(evt.alarms[0].description.language, language);
});
test("if simplify event can handle a missing language property", t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const description = "description";
const time = "20200729T130856Z";
const summary = "bla";
const _location = "Friedrichstrasse 3, 46145 Oberhausen Stadtmitte";
const uid = "abc";
const phone = "+491795345170";
const organizer = {
email: "john@smith.com",
commonName: "John Smith"
};
const vevent = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
SUMMARY:${summary}
LOCATION:${_location}
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
DESCRIPTION:${description}
TRIGGER;VALUE=DATE-TIME:${time}
SUMMARY:${summary}
END:VALARM
END:VEVENT
END:VCALENDAR`;
let evt = SimpleCalDAV.parseICS(vevent);
evt = SimpleCalDAV.simplifyEvent(evt, "uri");
t.truthy(evt.summary);
t.falsy(evt.summary.language);
t.false(evt.summary.hasOwnProperty("language"));
t.is(evt.summary.value, summary);
t.truthy(evt._location);
t.falsy(evt._location.language);
t.false(evt._location.hasOwnProperty("language"));
t.is(evt._location.value, _location);
t.truthy(evt.alarms[0].summary);
t.is(evt.alarms[0].summary.value, summary);
t.falsy(evt.alarms[0].summary.language);
t.false(evt.alarms[0].summary.hasOwnProperty("language"));
t.truthy(evt.alarms[0].description);
t.is(evt.alarms[0].description.value, description);
t.falsy(evt.alarms[0].description.language);
t.false(evt.alarms[0].description.hasOwnProperty("language"));
});
test("if simplify event can handle missing summary, summary, descriptions etc.", t => {
const action = "EMAIL";
const attendee = "attendee@mail.org";
const time = "20200729T130856Z";
const uid = "abc";
const phone = "+491795345170";
const organizer = {
email: "john@smith.com",
commonName: "John Smith"
};
const vevent = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//TimDaub//simple-caldav//EN
BEGIN:VEVENT
STATUS:CONFIRMED
UID:${uid}
DTSTART:20200729T180000Z
DTEND:20200729T183000Z
DTSTAMP:20200729T130856Z
ORGANIZER;CN=${organizer.commonName}:mailto:${organizer.email}
BEGIN:VALARM
ACTION:${action}
ATTENDEE:mailto:${attendee}
TRIGGER;VALUE=DATE-TIME:${time}
END:VALARM
END:VEVENT
END:VCALENDAR`;
let evt = SimpleCalDAV.parseICS(vevent);
evt = SimpleCalDAV.simplifyEvent(evt, "uri");
t.false(evt.hasOwnProperty("summary"));
t.false(evt.hasOwnProperty("_location"));
t.false(evt.alarms[0].hasOwnProperty("summary"));
t.false(evt.alarms[0].hasOwnProperty("description"));
});