Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var { NetUtil } = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs");
var { HttpServer } = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs");
var { MockRegistrar } = ChromeUtils.importESModule(
);
var {
CalDavGenericRequest,
CalDavItemRequest,
CalDavDeleteItemRequest,
CalDavPropfindRequest,
CalDavHeaderRequest,
CalDavPrincipalPropertySearchRequest,
CalDavOutboxRequest,
CalDavFreeBusyRequest,
} = ChromeUtils.importESModule("resource:///modules/caldav/CalDavRequest.sys.mjs");
var { CalDavWebDavSyncHandler } = ChromeUtils.importESModule(
);
var { CalDavSession } = ChromeUtils.importESModule(
);
var { CalDavXmlns } = ChromeUtils.importESModule("resource:///modules/caldav/CalDavUtils.sys.mjs");
var { Preferences } = ChromeUtils.importESModule("resource://gre/modules/Preferences.sys.mjs");
var { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs");
ChromeUtils.defineESModuleGetters(this, {
});
class LowerMap extends Map {
get(key) {
return super.get(key.toLowerCase());
}
set(key, value) {
return super.set(key.toLowerCase(), value);
}
}
var gServer;
var MockConflictPrompt = {
_origFunc: null,
overwrite: false,
register() {
if (!this._origFunc) {
this._origFunc = cal.provider.promptOverwrite;
cal.provider.promptOverwrite = () => {
return this.overwrite;
};
}
},
unregister() {
if (this._origFunc) {
cal.provider.promptOverwrite = this._origFunc;
this._origFunc = null;
}
},
};
class MockAlertsService {
QueryInterface = ChromeUtils.generateQI(["nsIAlertsService"]);
showAlert() {}
}
function replaceAlertsService() {
const originalAlertsServiceCID = MockRegistrar.register(
"@mozilla.org/alerts-service;1",
MockAlertsService
);
registerCleanupFunction(() => {
MockRegistrar.unregister(originalAlertsServiceCID);
});
}
var gMockCalendar = {
name: "xpcshell",
makeUri(insert, base) {
return base;
},
verboseLogging() {
return true;
},
ensureEncodedPath(x) {
return x;
},
ensureDecodedPath(x) {
return x;
},
startBatch() {},
endBatch() {},
addTargetCalendarItem() {},
finalizeUpdatedItems() {},
mHrefIndex: [],
};
gMockCalendar.superCalendar = gMockCalendar;
class CalDavServer {
constructor(calendarId) {
this.server = new HttpServer();
this.calendarId = calendarId;
this.session = new CalDavSession(this.calendarId, "xpcshell");
this.serverRequests = {};
this.server.registerPrefixHandler(
"/principals/",
this.router.bind(this, this.principals.bind(this))
);
this.server.registerPrefixHandler(
"/calendars/",
this.router.bind(this, this.calendars.bind(this))
);
this.server.registerPrefixHandler(
"/requests/",
this.router.bind(this, this.requests.bind(this))
);
}
start() {
this.server.start(-1);
registerCleanupFunction(() => this.server.stop(() => {}));
}
reset() {
this.serverRequests = {};
}
uri(path) {
const base = Services.io.newURI(`http://localhost:${this.server.identity.primaryPort}/`);
return Services.io.newURI(path, null, base);
}
router(nextHandler, request, response) {
try {
const method = request.method;
const parameters = new Map(request.queryString.split("&").map(part => part.split("=", 2)));
const available = request.bodyInputStream.available();
const body =
available > 0 ? NetUtil.readInputStreamToString(request.bodyInputStream, available) : null;
const headers = new LowerMap();
const headerIterator = function* (enumerator) {
while (enumerator.hasMoreElements()) {
yield enumerator.getNext().QueryInterface(Ci.nsISupportsString);
}
};
for (const hdr of headerIterator(request.headers)) {
headers.set(hdr.data, request.getHeader(hdr.data));
}
return nextHandler(request, response, method, headers, parameters, body);
} catch (e) {
info("Server Error: " + e.fileName + ":" + e.lineNumber + ": " + e + "\n");
return null;
}
}
resetClient(client) {
MockConflictPrompt.unregister();
cal.manager.unregisterCalendar(client);
}
waitForLoad(aCalendar) {
return new Promise((resolve, reject) => {
const observer = cal.createAdapter(Ci.calIObserver, {
onLoad() {
const uncached = aCalendar.wrappedJSObject.mUncachedCalendar.wrappedJSObject;
aCalendar.removeObserver(observer);
if (Components.isSuccessCode(uncached._lastStatus)) {
resolve(aCalendar);
} else {
reject(uncached._lastMessage);
}
},
});
aCalendar.addObserver(observer);
});
}
getClient() {
const uri = this.uri("/calendars/xpcshell/events");
const client = cal.manager.createCalendar("caldav", uri);
const uclient = client.wrappedJSObject;
client.name = "xpcshell";
client.setProperty("cache.enabled", true);
// Make sure we catch the last error message in case sync fails
monkeyPatch(uclient, "replayChangesOn", (protofunc, aListener) => {
protofunc({
onResult(operation, detail) {
uclient._lastStatus = operation.status;
uclient._lastMessage = detail;
aListener.onResult(operation, detail);
},
});
});
cal.manager.registerCalendar(client);
const cachedCalendar = cal.manager.getCalendarById(client.id);
return this.waitForLoad(cachedCalendar);
}
principals(request, response, method, headers, parameters, body) {
this.serverRequests.principals = { method, headers, parameters, body };
if (method == "REPORT" && request.path == "/principals/") {
response.setHeader("Content-Type", "application/xml");
response.write(dedent`
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:" xmlns:B="http://BigCorp.com/ns/">
<D:response>
<D:propstat>
<D:prop>
<D:displayname>John Doe</D:displayname>
<B:department>Widget Sales</B:department>
<B:phone>234-4567</B:phone>
<B:office>209</B:office>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<B:salary/>
</D:prop>
<D:status>HTTP/1.1 403 Forbidden</D:status>
</D:propstat>
</D:response>
<D:response>
<D:propstat>
<D:prop>
<D:displayname>Zygdoebert Smith</D:displayname>
<B:department>Gadget Sales</B:department>
<B:phone>234-7654</B:phone>
<B:office>114</B:office>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<B:salary/>
</D:prop>
<D:status>HTTP/1.1 403 Forbidden</D:status>
</D:propstat>
</D:response>
</D:multistatus>
`);
response.setStatusLine(null, 207, "Multistatus");
} else if (method == "PROPFIND" && request.path == "/principals/xpcshell/user/") {
response.setHeader("Content-Type", "application/xml");
response.write(dedent`
<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:response>
<D:href>${this.uri("/principals/xpcshell/user").spec}</D:href>
<D:propstat>
<D:prop>
<C:calendar-home-set>
<D:href>${this.uri("/calendars/xpcshell/user/").spec}</D:href>
</C:calendar-home-set>
<C:calendar-user-address-set>
<D:href>mailto:xpcshell@example.com</D:href>
</C:calendar-user-address-set>
<C:schedule-inbox-URL>
<D:href>${this.uri("/calendars/xpcshell/inbox").spec}/</D:href>
</C:schedule-inbox-URL>
<C:schedule-outbox-URL>
<D:href>${this.uri("/calendars/xpcshell/outbox").spec}</D:href>
</C:schedule-outbox-URL>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
`);
response.setStatusLine(null, 207, "Multistatus");
}
}
calendars(request, response, method, headers, parameters, body) {
this.serverRequests.calendars = { method, headers, parameters, body };
if (
method == "PROPFIND" &&
request.path.startsWith("/calendars/xpcshell/events") &&
headers.get("depth") == 0
) {
response.setHeader("Content-Type", "application/xml");
response.write(dedent`
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus ${CalDavXmlns("D", "C", "CS")} xmlns:R="http://www.foo.bar/boxschema/">
<D:response>
<D:href>${request.path}</D:href>
<D:propstat>
<D:prop>
<D:resourcetype>
<D:collection/>
<C:calendar/>
</D:resourcetype>
<R:plain-text-prop>hello, world</R:plain-text-prop>
<D:principal-collection-set>
<D:href>${this.uri("/principals/").spec}</D:href>
<D:href>${this.uri("/principals/subthing/").spec}</D:href>
</D:principal-collection-set>
<D:current-user-principal>
<D:href>${this.uri("/principals/xpcshell/user").spec}</D:href>
</D:current-user-principal>
<D:supported-report-set>
<D:supported-report>
<D:report>
<D:principal-property-search/>
</D:report>
</D:supported-report>
<D:supported-report>
<D:report>
<C:calendar-multiget/>
</D:report>
</D:supported-report>
<D:supported-report>
<D:report>
<D:sync-collection/>
</D:report>
</D:supported-report>
</D:supported-report-set>
<C:supported-calendar-component-set>
<C:comp name="VEVENT"/>
<C:comp name="VTODO"/>
</C:supported-calendar-component-set>
<C:schedule-inbox-URL>
<D:href>${this.uri("/calendars/xpcshell/inbox").spec}</D:href>
</C:schedule-inbox-URL>
<C:schedule-outbox-URL>
${this.uri("/calendars/xpcshell/outbox").spec}
</C:schedule-outbox-URL>
<CS:getctag>1413647159-1007960</CS:getctag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<R:obscure-thing-not-found/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
</D:multistatus>
`);
response.setStatusLine(null, 207, "Multistatus");
} else if (method == "POST" && request.path == "/calendars/xpcshell/outbox") {
response.setHeader("Content-Type", "application/xml");
response.write(dedent`
<?xml version="1.0" encoding="utf-8" ?>
<C:schedule-response ${CalDavXmlns("D", "C")}>
<D:response>
<D:href>mailto:recipient1@example.com</D:href>
<D:request-status>2.0;Success</D:request-status>
</D:response>
<D:response>
<D:href>mailto:recipient2@example.com</D:href>
<D:request-status>2.0;Success</D:request-status>
</D:response>
</C:schedule-response>
`);
response.setStatusLine(null, 200, "OK");
} else if (method == "POST" && request.path == "/calendars/xpcshell/outbox2") {
response.setHeader("Content-Type", "application/xml");
response.write(dedent`
<?xml version="1.0" encoding="utf-8" ?>
<C:schedule-response ${CalDavXmlns("D", "C")}>
<D:response>
<D:recipient>
<D:href>mailto:recipient1@example.com</D:href>
</D:recipient>
<D:request-status>2.0;Success</D:request-status>
<C:calendar-data>
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
METHOD:REQUEST
BEGIN:VFREEBUSY
DTSTART;VALUE=DATE:20180102
DTEND;VALUE=DATE:20180126
ORGANIZER:mailto:xpcshell@example.com
ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL:mail
to:recipient@example.com
FREEBUSY;FBTYPE=FREE:20180103T010101Z/20180117T010101Z
FREEBUSY;FBTYPE=BUSY:20180118T010101Z/P7D
END:VFREEBUSY
END:VCALENDAR
</C:calendar-data>
</D:response>
</C:schedule-response>
`);
response.setStatusLine(null, 200, "OK");
} else if (method == "OPTIONS" && request.path == "/calendars/xpcshell/") {
response.setHeader(
"DAV",
"1, 2, 3, access-control, extended-mkcol, resource-sharing, calendar-access, calendar-auto-schedule, calendar-query-extended, calendar-availability, calendarserver-sharing, inbox-availability"
);
response.setStatusLine(null, 200, "OK");
} else if (method == "REPORT" && request.path == "/calendars/xpcshell/events/") {
response.setHeader("Content-Type", "application/xml");
const bodydom = cal.xml.parseString(body);
const report = bodydom.documentElement.localName;
const eventName = String.fromCharCode(...new TextEncoder().encode("イベント"));
if (report == "sync-collection") {
response.write(dedent`
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus ${CalDavXmlns("D")}>
<D:response>
<D:href>${this.uri("/calendars/xpcshell/events/test.ics").spec}</D:href>
<D:propstat>
<D:prop>
<D:getcontenttype>text/calendar; charset=utf-8; component=VEVENT</D:getcontenttype>
<D:getetag>"2decee6ffb701583398996bfbdacb8eec53edf94"</D:getetag>
<D:displayname>${eventName}</D:displayname>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
`);
} else if (report == "calendar-multiget") {
const event = new CalEvent();
event.title = "会議";
event.startDate = cal.dtz.now();
event.endDate = cal.dtz.now();
const icalString = String.fromCharCode(...new TextEncoder().encode(event.icalString));
response.write(dedent`
<?xml version="1.0" encoding="utf-8"?>
<D:multistatus ${CalDavXmlns("D", "C")}>
<D:response>
<D:href>${this.uri("/calendars/xpcshell/events/test.ics").spec}</D:href>
<D:propstat>
<D:prop>
<D:getetag>"2decee6ffb701583398996bfbdacb8eec53edf94"</D:getetag>
<C:calendar-data>${icalString}</C:calendar-data>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
`);
}
response.setStatusLine(null, 207, "Multistatus");
} else {
console.log("XXX: " + method, request.path, [...headers.entries()]);
}
}
requests(request, response, method, headers, parameters, body) {
// ["", "requests", "generic"] := /requests/generic
const parts = request.path.split("/");
const id = parts[2];
let status = parseInt(parts[3] || "", 10) || 200;
if (id == "redirected") {
response.setHeader("Location", "/requests/redirected-target", false);
status = 302;
} else if (id == "dav") {
response.setHeader("DAV", "1, calendar-schedule, calendar-auto-schedule");
}
this.serverRequests[id] = { method, headers, parameters, body };
for (const [hdr, value] of headers.entries()) {
response.setHeader(hdr, "response-" + value, false);
}
response.setHeader("Content-Type", "application/xml");
response.write(`<response id="${id}">xpc</response>`);
response.setStatusLine(null, status, null);
}
}
function run_test() {
Preferences.set("calendar.debug.log", true);
Preferences.set("calendar.debug.log.verbose", true);
cal.console.maxLogLevel = "debug";
replaceAlertsService();
// TODO: make do_calendar_startup to work with this test and replace the startup code here
do_get_profile();
do_test_pending();
cal.manager.startup({
onResult() {
gServer = new CalDavServer("xpcshell@example.com");
gServer.start();
cal.timezoneService.startup({
onResult() {
run_next_test();
do_test_finished();
},
});
},
});
}
add_task(async function test_caldav_session() {
gServer.reset();
let prepared = 0;
let redirected = 0;
let completed = 0;
let restart = false;
gServer.session.authAdapters.localhost = {
async prepareRequest() {
prepared++;
},
async prepareRedirect() {
redirected++;
},
async completeRequest() {
completed++;
if (restart) {
restart = false;
return CalDavSession.RESTART_REQUEST;
}
return null;
},
};
// First a simple request
let uri = gServer.uri("/requests/session");
let request = new CalDavGenericRequest(gServer.session, gMockCalendar, "HEAD", uri);
await request.commit();
equal(prepared, 1);
equal(redirected, 0);
equal(completed, 1);
// Now a redirect
prepared = redirected = completed = 0;
uri = gServer.uri("/requests/redirected");
request = new CalDavGenericRequest(gServer.session, gMockCalendar, "HEAD", uri);
await request.commit();
equal(prepared, 1);
equal(redirected, 1);
equal(completed, 1);
// Now with restarting the request
prepared = redirected = completed = 0;
restart = true;
uri = gServer.uri("/requests/redirected");
request = new CalDavGenericRequest(gServer.session, gMockCalendar, "HEAD", uri);
await request.commit();
equal(prepared, 2);
equal(redirected, 2);
equal(completed, 2);
});
/**
* This test covers both GenericRequest and the base class CalDavRequestBase/CalDavResponseBase
*/
add_task(async function test_generic_request() {
gServer.reset();
const uri = gServer.uri("/requests/generic");
const headers = { "X-Hdr": "exists" };
const request = new CalDavGenericRequest(
gServer.session,
gMockCalendar,
"PUT",
uri,
headers,
"<body>xpc</body>",
"text/plain"
);
strictEqual(request.uri.spec, uri.spec);
strictEqual(request.session.id, gServer.session.id);
strictEqual(request.calendar, gMockCalendar);
strictEqual(request.uploadData, "<body>xpc</body>");
strictEqual(request.contentType, "text/plain");
strictEqual(request.response, null);
strictEqual(request.getHeader("X-Hdr"), null); // Only works after commit
const response = await request.commit();
ok(!!request.response);
equal(request.getHeader("X-Hdr"), "exists");
equal(response.uri.spec, uri.spec);
ok(!response.redirected);
equal(response.status, 200);
equal(response.statusCategory, 2);
ok(response.ok);
ok(!response.clientError);
ok(!response.conflict);
ok(!response.notFound);
ok(!response.serverError);
equal(response.text, '<response id="generic">xpc</response>');
equal(response.xml.documentElement.localName, "response");
equal(response.getHeader("X-Hdr"), "response-exists");
const serverResult = gServer.serverRequests.generic;
equal(serverResult.method, "PUT");
equal(serverResult.headers.get("x-hdr"), "exists");
equal(serverResult.headers.get("content-type"), "text/plain");
equal(serverResult.body, "<body>xpc</body>");
});
add_task(async function test_generic_redirected_request() {
gServer.reset();
const uri = gServer.uri("/requests/redirected");
const headers = {
Depth: 1,
Originator: "o",
Recipient: "r",
"If-None-Match": "*",
"If-Match": "123",
};
const request = new CalDavGenericRequest(
gServer.session,
gMockCalendar,
"PUT",
uri,
headers,
"<body>xpc</body>",
"text/plain"
);
const response = await request.commit();
ok(response.redirected);
equal(response.status, 200);
equal(response.text, '<response id="redirected-target">xpc</response>');
equal(response.xml.documentElement.getAttribute("id"), "redirected-target");
ok(gServer.serverRequests.redirected);
ok(gServer.serverRequests["redirected-target"]);
let results = gServer.serverRequests.redirected;
equal(results.headers.get("Depth"), 1);
equal(results.headers.get("Originator"), "o");
equal(results.headers.get("Recipient"), "r");
equal(results.headers.get("If-None-Match"), "*");
equal(results.headers.get("If-Match"), "123");
results = gServer.serverRequests["redirected-target"];
equal(results.headers.get("Depth"), 1);
equal(results.headers.get("Originator"), "o");
equal(results.headers.get("Recipient"), "r");
equal(results.headers.get("If-None-Match"), "*");
equal(results.headers.get("If-Match"), "123");
equal(response.lastRedirectStatus, 302);
});
add_task(async function test_item_request() {
gServer.reset();
let uri = gServer.uri("/requests/item/201");
const icalString = "BEGIN:VEVENT\r\nUID:123\r\nEND:VEVENT";
const componentString = `BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\n${icalString}\r\nEND:VCALENDAR\r\n`;
let request = new CalDavItemRequest(
gServer.session,
gMockCalendar,
uri,
new CalEvent(icalString),
"*"
);
let response = await request.commit();
equal(response.status, 201);
ok(response.ok);
let serverResult = gServer.serverRequests.item;
equal(serverResult.method, "PUT");
equal(serverResult.body, componentString);
equal(serverResult.headers.get("If-None-Match"), "*");
ok(!serverResult.headers.has("If-Match"));
// Now the same with 204 No Content and an etag
gServer.reset();
uri = gServer.uri("/requests/item/204");
request = new CalDavItemRequest(
gServer.session,
gMockCalendar,
uri,
new CalEvent(icalString),
"123123"
);
response = await request.commit();
equal(response.status, 204);
ok(response.ok);
serverResult = gServer.serverRequests.item;
equal(serverResult.method, "PUT");
equal(serverResult.body, componentString);
equal(serverResult.headers.get("If-Match"), "123123");
ok(!serverResult.headers.has("If-None-Match"));
// Now the same with 200 OK and no etag
gServer.reset();
uri = gServer.uri("/requests/item/200");
request = new CalDavItemRequest(gServer.session, gMockCalendar, uri, new CalEvent(icalString));
response = await request.commit();
equal(response.status, 200);
ok(response.ok);
serverResult = gServer.serverRequests.item;
equal(serverResult.method, "PUT");
equal(serverResult.body, componentString);
ok(!serverResult.headers.has("If-Match"));
ok(!serverResult.headers.has("If-None-Match"));
});
add_task(async function test_delete_item_request() {
gServer.reset();
let uri = gServer.uri("/requests/deleteitem");
let request = new CalDavDeleteItemRequest(gServer.session, gMockCalendar, uri, "*");
strictEqual(request.uploadData, null);
strictEqual(request.contentType, null);
let response = await request.commit();
equal(response.status, 200);
ok(response.ok);
let serverResult = gServer.serverRequests.deleteitem;
equal(serverResult.method, "DELETE");
equal(serverResult.headers.get("If-Match"), "*");
ok(!serverResult.headers.has("If-None-Match"));
// Now the same with no etag, and a (valid) 404 response
gServer.reset();
uri = gServer.uri("/requests/deleteitem/404");
request = new CalDavDeleteItemRequest(gServer.session, gMockCalendar, uri);
response = await request.commit();
equal(response.status, 404);
ok(response.ok);
serverResult = gServer.serverRequests.deleteitem;
equal(serverResult.method, "DELETE");
ok(!serverResult.headers.has("If-Match"));
ok(!serverResult.headers.has("If-None-Match"));
});
add_task(async function test_propfind_request() {
gServer.reset();
const uri = gServer.uri("/calendars/xpcshell/events");
const props = [
"D:principal-collection-set",
"D:current-user-principal",
"D:supported-report-set",
"C:supported-calendar-component-set",
"C:schedule-inbox-URL",
"C:schedule-outbox-URL",
"R:obscure-thing-not-found",
];
const request = new CalDavPropfindRequest(gServer.session, gMockCalendar, uri, props);
const response = await request.commit();
equal(response.status, 207);
ok(response.ok);
const results = gServer.serverRequests.calendars;
ok(
results.body.match(/<D:prop>\s*<D:principal-collection-set\/>\s*<D:current-user-principal\/>/)
);
equal(Object.keys(response.data).length, 1);
ok(!!response.data[uri.filePath]);
ok(!!response.firstProps);
const resprops = response.firstProps;
deepEqual(resprops["D:principal-collection-set"], [
gServer.uri("/principals/").spec,
gServer.uri("/principals/subthing/").spec,
]);
equal(resprops["D:current-user-principal"], gServer.uri("/principals/xpcshell/user").spec);
deepEqual(
[...resprops["D:supported-report-set"].values()],
["D:principal-property-search", "C:calendar-multiget", "D:sync-collection"]
);
deepEqual([...resprops["C:supported-calendar-component-set"].values()], ["VEVENT", "VTODO"]);
equal(resprops["C:schedule-inbox-URL"], gServer.uri("/calendars/xpcshell/inbox").spec);
equal(resprops["C:schedule-outbox-URL"], gServer.uri("/calendars/xpcshell/outbox").spec);
strictEqual(resprops["R:obscure-thing-not-found"], null);
equal(resprops["R:plain-text-prop"], "hello, world");
});
add_task(async function test_davheader_request() {
gServer.reset();
const uri = gServer.uri("/requests/dav");
const request = new CalDavHeaderRequest(gServer.session, gMockCalendar, uri);
const response = await request.commit();
const serverResult = gServer.serverRequests.dav;
equal(serverResult.method, "OPTIONS");
deepEqual([...response.features], ["calendar-schedule", "calendar-auto-schedule"]);
strictEqual(response.version, 1);
});
add_task(async function test_propsearch_request() {
gServer.reset();
const uri = gServer.uri("/principals/");
const props = ["D:displayname", "B:department", "B:phone", "B:office"];
const request = new CalDavPrincipalPropertySearchRequest(
gServer.session,
gMockCalendar,
uri,
"doE",
"D:displayname",
props
);
const response = await request.commit();
equal(response.status, 207);
ok(response.ok);
equal(response.data["http://www.example.com/users/jdoe"]["D:displayname"], "John Doe");
ok(gServer.serverRequests.principals.body.includes("<D:match>doE</D:match>"));
ok(gServer.serverRequests.principals.body.match(/<D:prop>\s*<D:displayname\/>\s*<\/D:prop>/));
ok(
gServer.serverRequests.principals.body.match(/<D:prop>\s*<D:displayname\/>\s*<B:department\/>/)
);
});
add_task(async function test_outbox_request() {
gServer.reset();
const icalString = "BEGIN:VEVENT\r\nUID:123\r\nEND:VEVENT";
const uri = gServer.uri("/calendars/xpcshell/outbox");
const request = new CalDavOutboxRequest(
gServer.session,
gMockCalendar,
uri,
"xpcshell@example.com",
["recipient1@example.com", "recipient2@example.com"],
"REPLY",
new CalEvent(icalString)
);
const response = await request.commit();
equal(response.status, 200);
ok(response.ok);
const results = gServer.serverRequests.calendars;
ok(results.body.includes("METHOD:REPLY"));
equal(results.method, "POST");
equal(results.headers.get("Originator"), "xpcshell@example.com");
equal(results.headers.get("Recipient"), "recipient1@example.com, recipient2@example.com");
});
add_task(async function test_freebusy_request() {
gServer.reset();
const uri = gServer.uri("/calendars/xpcshell/outbox2");
const request = new CalDavFreeBusyRequest(
gServer.session,
gMockCalendar,
uri,
"mailto:xpcshell@example.com",
"mailto:recipient@example.com",
cal.createDateTime("20180101"),
cal.createDateTime("20180201")
);
const response = await request.commit();
equal(response.status, 200);
ok(response.ok);
const results = gServer.serverRequests.calendars;
equal(
ics_unfoldline(
results.body
.replace(/\r\n/g, "\n")
.replace(/(UID|DTSTAMP):[^\n]+\n/g, "")
.trim()
),
dedent`
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
METHOD:REQUEST
BEGIN:VFREEBUSY
DTSTART;VALUE=DATE:20180101
DTEND;VALUE=DATE:20180201
ORGANIZER:mailto:xpcshell@example.com
ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL:mailto:recipient@example.com
END:VFREEBUSY
END:VCALENDAR
`
);
equal(results.method, "POST");
equal(results.headers.get("Content-Type"), "text/calendar; charset=utf-8");
equal(results.headers.get("Originator"), "mailto:xpcshell@example.com");
equal(results.headers.get("Recipient"), "mailto:recipient@example.com");
const first = response.firstRecipient;
equal(first.status, "2.0;Success");
deepEqual(
first.intervals.map(interval => interval.type),
["UNKNOWN", "FREE", "BUSY", "UNKNOWN"]
);
deepEqual(
first.intervals.map(interval => interval.begin.icalString + ":" + interval.end.icalString),
[
"20180101:20180102",
"20180103T010101Z:20180117T010101Z",
"20180118T010101Z:20180125T010101Z",
"20180126:20180201",
]
);
});
add_task(async function test_caldav_client() {
const client = await gServer.getClient();
const items = await client.getItemsAsArray(Ci.calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null);
equal(items.length, 1);
equal(items[0].title, "会議");
});
/**
* Test non-ASCII text in the XML response is parsed correctly in CalDavWebDavSyncHandler.
*/
add_task(async function test_caldav_sync() {
gServer.reset();
const uri = gServer.uri("/calendars/xpcshell/events/");
gMockCalendar.session = gServer.session;
const webDavSync = new CalDavWebDavSyncHandler(gMockCalendar, uri);
await webDavSync.doWebDAVSync();
ok(webDavSync.logXML.includes("イベント"), "Non-ASCII text should be parsed correctly");
});
add_task(function test_can_get_google_adapter() {
// Initialize a session with bogus values
const session = new CalDavSession("xpcshell@example.com", "xpcshell");
// We don't have a facility for actually testing our Google CalDAV requests,
// but we can at least verify that the adapter looks okay at a glance
equal(
session.authAdapters["apidata.googleusercontent.com"].authorizationEndpoint,
);
});