Bug 1608304 part 2 - Create a mock LDAP server and some tests; r=mkmelin
--HG-- extra : rebase_source : 2f9dc90f9e48b6d4c16c94e21bbec030f1c872f4 extra : histedit_source : 351a811ea0a92e6f575068847b39d3a46579f61e%2C85d9621b83f5f9dc217d572fc215e27915a9e76f
This commit is contained in:
Родитель
2a041ddaab
Коммит
decc05d54d
|
@ -142,6 +142,7 @@ var gAddressBookAbViewListener = {
|
|||
},
|
||||
onCountChanged(total) {
|
||||
SetStatusText(total);
|
||||
window.dispatchEvent(new CustomEvent("countchange"));
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -11,4 +11,6 @@ prefs =
|
|||
mailnews.start_page.url=about:blank
|
||||
subsuite = thunderbird
|
||||
|
||||
[browser_ldap_search.js]
|
||||
support-files = ../../../../../mailnews/addrbook/test/unit/data/ldap_contacts.json
|
||||
[browser_mailing_lists.js]
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/* 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/. */
|
||||
|
||||
const { LDAPServer } = ChromeUtils.import(
|
||||
"resource://testing-common/LDAPServer.jsm"
|
||||
);
|
||||
const { mailTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/mailnews/mailTestUtils.js"
|
||||
);
|
||||
|
||||
const jsonFile =
|
||||
"http://mochi.test:8888/browser/comm/mail/components/addrbook/test/browser/ldap_contacts.json";
|
||||
|
||||
add_task(async () => {
|
||||
LDAPServer.open();
|
||||
let response = await fetch(jsonFile);
|
||||
let ldapContacts = await response.json();
|
||||
|
||||
let bookPref = MailServices.ab.newAddressBook(
|
||||
"Mochitest",
|
||||
`ldap://localhost:${LDAPServer.port}/`,
|
||||
0
|
||||
);
|
||||
let book = MailServices.ab.getDirectoryFromId(bookPref);
|
||||
|
||||
let abWindow = await openAddressBookWindow();
|
||||
let abDocument = abWindow.document;
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
abWindow.close();
|
||||
MailServices.ab.deleteAddressBook(book.URI);
|
||||
LDAPServer.close();
|
||||
});
|
||||
|
||||
let dirTree = abDocument.getElementById("dirTree");
|
||||
is(dirTree.view.getCellText(2, dirTree.columns[0]), "Mochitest");
|
||||
mailTestUtils.treeClick(EventUtils, abWindow, dirTree, 2, 0, {});
|
||||
|
||||
let resultsTree = abDocument.getElementById("abResultsTree");
|
||||
|
||||
let searchBox = abDocument.getElementById("peopleSearchInput");
|
||||
EventUtils.synthesizeMouseAtCenter(searchBox, {}, abWindow);
|
||||
EventUtils.sendString("holmes", abWindow);
|
||||
|
||||
await LDAPServer.read(); // BindRequest
|
||||
is(resultsTree.view.rowCount, 0);
|
||||
LDAPServer.writeBindResponse();
|
||||
|
||||
await LDAPServer.read(); // SearchRequest
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.mycroft);
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.sherlock);
|
||||
LDAPServer.writeSearchResultDone();
|
||||
|
||||
await new Promise(resolve => {
|
||||
abWindow.addEventListener("countchange", function onCountChange() {
|
||||
if (resultsTree.view && resultsTree.view.rowCount == 2) {
|
||||
abWindow.removeEventListener("countchange", onCountChange);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
is(resultsTree.view.rowCount, 2);
|
||||
is(resultsTree.view.getCellText(0, resultsTree.columns[0]), "Mycroft Holmes");
|
||||
is(
|
||||
resultsTree.view.getCellText(1, resultsTree.columns[0]),
|
||||
"Sherlock Holmes"
|
||||
);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(searchBox, {}, abWindow);
|
||||
EventUtils.synthesizeKey("a", { accelKey: true }, abWindow);
|
||||
EventUtils.sendString("john", abWindow);
|
||||
|
||||
await LDAPServer.read(); // BindRequest
|
||||
is(resultsTree.view.rowCount, 0);
|
||||
LDAPServer.writeBindResponse();
|
||||
|
||||
await LDAPServer.read(); // SearchRequest
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.john);
|
||||
LDAPServer.writeSearchResultDone();
|
||||
|
||||
await new Promise(resolve => {
|
||||
abWindow.addEventListener("countchange", function onCountChange() {
|
||||
if (resultsTree.view && resultsTree.view.rowCount == 1) {
|
||||
abWindow.removeEventListener("countchange", onCountChange);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
is(resultsTree.view.rowCount, 1);
|
||||
is(resultsTree.view.getCellText(0, resultsTree.columns[0]), "John Watson");
|
||||
});
|
|
@ -0,0 +1,280 @@
|
|||
/* 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/. */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["LDAPServer"];
|
||||
const PRINT_DEBUG = false;
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/**
|
||||
* This is a partial implementation of an LDAP server as defined by RFC 4511.
|
||||
* It's not intended to serve any particular dataset, rather, tests should
|
||||
* cause the application to make requests and tell the server what to respond.
|
||||
*
|
||||
* https://docs.ldap.com/specs/rfc4511.txt
|
||||
*
|
||||
* @implements nsIInputStreamCallback
|
||||
* @implements nsIServerSocketListener
|
||||
*/
|
||||
var LDAPServer = {
|
||||
serverSocket: null,
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIInputStreamCallback,
|
||||
Ci.nsIServerSocketListener,
|
||||
]),
|
||||
|
||||
/**
|
||||
* Start listening on an OS-selected port. The port number can be found at
|
||||
* LDAPServer.port.
|
||||
*/
|
||||
open() {
|
||||
this.serverSocket = Cc[
|
||||
"@mozilla.org/network/server-socket;1"
|
||||
].createInstance(Ci.nsIServerSocket);
|
||||
this.serverSocket.init(-1, true, 1);
|
||||
console.log(`socket open on port ${this.serverSocket.port}`);
|
||||
|
||||
this.serverSocket.asyncListen(this);
|
||||
},
|
||||
/**
|
||||
* Stop listening for new connections and close any that are open.
|
||||
*/
|
||||
close() {
|
||||
this.serverSocket.close();
|
||||
},
|
||||
/**
|
||||
* The port this server is listening on.
|
||||
*/
|
||||
get port() {
|
||||
return this.serverSocket.port;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves any data sent to the server since connection or the previous
|
||||
* call to read(). This should be called every time the application is
|
||||
* expected to send data.
|
||||
*
|
||||
* @returns {Promise} Resolves when data is received by the server, with the
|
||||
* data as a byte array.
|
||||
*/
|
||||
read() {
|
||||
return new Promise(resolve => {
|
||||
if (this._data) {
|
||||
resolve(this._data);
|
||||
delete this._data;
|
||||
}
|
||||
this._inputStreamReadyResolve = resolve;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Sends raw data to the application. Generally this shouldn't be used
|
||||
* directly but it may be useful for testing.
|
||||
*
|
||||
* @param {byte array} Data
|
||||
*/
|
||||
write(data) {
|
||||
if (PRINT_DEBUG) {
|
||||
console.log(
|
||||
">>> " + data.map(b => b.toString(16).padStart(2, 0)).join(" ")
|
||||
);
|
||||
}
|
||||
this._outputStream.writeByteArray(data);
|
||||
},
|
||||
/**
|
||||
* Sends a simple BindResponse to the application.
|
||||
* See section 4.2.2 of the RFC.
|
||||
*/
|
||||
writeBindResponse() {
|
||||
let message = new Sequence(0x30, new IntegerValue(this._lastMessageID));
|
||||
let person = new Sequence(
|
||||
0x61,
|
||||
new EnumeratedValue(0),
|
||||
new StringValue(""),
|
||||
new StringValue("")
|
||||
);
|
||||
message.children.push(person);
|
||||
this.write(message.getBytes());
|
||||
},
|
||||
/**
|
||||
* Sends a SearchResultEntry to the application.
|
||||
* See section 4.5.2 of the RFC.
|
||||
*
|
||||
* @param {object} An object representing a person. Keys of the object are:
|
||||
* - dn The LDAP DN of the person
|
||||
* - attributes A key/value or key/array-of-values object
|
||||
* representing the person
|
||||
*/
|
||||
writeSearchResultEntry({ dn, attributes }) {
|
||||
let message = new Sequence(0x30, new IntegerValue(this._lastMessageID));
|
||||
|
||||
let person = new Sequence(0x64, new StringValue(dn));
|
||||
message.children.push(person);
|
||||
|
||||
let attributeSequence = new Sequence(0x30);
|
||||
person.children.push(attributeSequence);
|
||||
|
||||
for (let [key, value] of Object.entries(attributes)) {
|
||||
let seq = new Sequence(0x30, new StringValue(key), new Sequence(0x31));
|
||||
if (typeof value == "string") {
|
||||
value = [value];
|
||||
}
|
||||
for (let v of value) {
|
||||
seq.children[1].children.push(new StringValue(v));
|
||||
}
|
||||
attributeSequence.children.push(seq);
|
||||
}
|
||||
|
||||
this.write(message.getBytes());
|
||||
},
|
||||
/**
|
||||
* Sends a SearchResultDone to the application.
|
||||
* See section 4.5.2 of the RFC.
|
||||
*/
|
||||
writeSearchResultDone() {
|
||||
let message = new Sequence(0x30, new IntegerValue(this._lastMessageID));
|
||||
let person = new Sequence(
|
||||
0x65,
|
||||
new EnumeratedValue(0),
|
||||
new StringValue(""),
|
||||
new StringValue("")
|
||||
);
|
||||
message.children.push(person);
|
||||
this.write(message.getBytes());
|
||||
},
|
||||
|
||||
/**
|
||||
* nsIServerSocketListener.onSocketAccepted
|
||||
*/
|
||||
onSocketAccepted(socket, transport) {
|
||||
let inputStream = transport
|
||||
.openInputStream(0, 8192, 1024)
|
||||
.QueryInterface(Ci.nsIAsyncInputStream);
|
||||
|
||||
let outputStream = transport.openOutputStream(0, 0, 0);
|
||||
this._outputStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
|
||||
Ci.nsIBinaryOutputStream
|
||||
);
|
||||
this._outputStream.setOutputStream(outputStream);
|
||||
|
||||
if (this._socketConnectedResolve) {
|
||||
this._socketConnectedResolve();
|
||||
delete this._socketConnectedResolve;
|
||||
}
|
||||
inputStream.asyncWait(this, 0, 0, Services.tm.mainThread);
|
||||
},
|
||||
/**
|
||||
* nsIServerSocketListener.onStopListening
|
||||
*/
|
||||
onStopListening(socket, status) {
|
||||
console.log(`socket closed with status ${status.toString(16)}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* nsIInputStreamCallback.onInputStreamReady
|
||||
*/
|
||||
onInputStreamReady(stream) {
|
||||
let available;
|
||||
try {
|
||||
available = stream.available();
|
||||
} catch (ex) {
|
||||
if (
|
||||
[Cr.NS_BASE_STREAM_CLOSED, Cr.NS_ERROR_NET_RESET].includes(ex.result)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
|
||||
let binaryInputStream = Cc[
|
||||
"@mozilla.org/binaryinputstream;1"
|
||||
].createInstance(Ci.nsIBinaryInputStream);
|
||||
binaryInputStream.setInputStream(stream);
|
||||
let data = binaryInputStream.readByteArray(available);
|
||||
if (PRINT_DEBUG) {
|
||||
console.log(
|
||||
"<<< " + data.map(b => b.toString(16).padStart(2, 0)).join(" ")
|
||||
);
|
||||
}
|
||||
this._lastMessageID = data[4];
|
||||
|
||||
if (this._inputStreamReadyResolve) {
|
||||
this._inputStreamReadyResolve(data);
|
||||
delete this._inputStreamReadyResolve;
|
||||
} else {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
stream.asyncWait(this, 0, 0, Services.tm.mainThread);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper classes to convert primitives to LDAP byte sequences.
|
||||
*/
|
||||
|
||||
class Sequence {
|
||||
constructor(number, ...children) {
|
||||
this.number = number;
|
||||
this.children = children;
|
||||
}
|
||||
getBytes() {
|
||||
let bytes = [];
|
||||
for (let c of this.children) {
|
||||
bytes = bytes.concat(c.getBytes());
|
||||
}
|
||||
return [this.number].concat(getLengthBytes(bytes.length), bytes);
|
||||
}
|
||||
}
|
||||
class IntegerValue {
|
||||
constructor(int) {
|
||||
this.int = int;
|
||||
this.number = 0x02;
|
||||
}
|
||||
getBytes() {
|
||||
let temp = this.int;
|
||||
let bytes = [];
|
||||
|
||||
while (temp >= 128) {
|
||||
bytes.unshift(temp & 255);
|
||||
temp >>= 8;
|
||||
}
|
||||
bytes.unshift(temp);
|
||||
return [this.number].concat(getLengthBytes(bytes.length), bytes);
|
||||
}
|
||||
}
|
||||
class StringValue {
|
||||
constructor(str) {
|
||||
this.str = str;
|
||||
}
|
||||
getBytes() {
|
||||
return [0x04].concat(
|
||||
getLengthBytes(this.str.length),
|
||||
Array.from(this.str, c => c.charCodeAt(0))
|
||||
);
|
||||
}
|
||||
}
|
||||
class EnumeratedValue extends IntegerValue {
|
||||
constructor(int) {
|
||||
super(int);
|
||||
this.number = 0x0a;
|
||||
}
|
||||
}
|
||||
|
||||
function getLengthBytes(int) {
|
||||
if (int < 128) {
|
||||
return [int];
|
||||
}
|
||||
|
||||
let temp = int;
|
||||
let bytes = [];
|
||||
|
||||
while (temp >= 128) {
|
||||
bytes.unshift(temp & 255);
|
||||
temp >>= 8;
|
||||
}
|
||||
bytes.unshift(temp);
|
||||
bytes.unshift(0x80 | bytes.length);
|
||||
return bytes;
|
||||
}
|
|
@ -3,6 +3,10 @@
|
|||
# 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/.
|
||||
|
||||
TESTING_JS_MODULES += [
|
||||
'LDAPServer.jsm',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'unit/xpcshell.ini',
|
||||
'unit/xpcshell_migration.ini',
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"eurus": {
|
||||
"dn": "uid=eurus,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Eurus Holmes",
|
||||
"givenName": "Eurus",
|
||||
"mail": "eurus@bakerstreet.invalid",
|
||||
"sn": "Holmes"
|
||||
}
|
||||
},
|
||||
"irene": {
|
||||
"dn": "uid=irene,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Irene Adler",
|
||||
"givenName": "irene",
|
||||
"mail": "irene@bakerstreet.invalid",
|
||||
"sn": "Adler"
|
||||
}
|
||||
},
|
||||
"john": {
|
||||
"dn": "uid=john,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "John Watson",
|
||||
"givenName": "John",
|
||||
"mail": "john@bakerstreet.invalid",
|
||||
"sn": "Watson"
|
||||
}
|
||||
},
|
||||
"lestrade": {
|
||||
"dn": "uid=lestrade,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Greg Lestrade",
|
||||
"givenName": "Greg",
|
||||
"mail": "lestrade@bakerstreet.invalid",
|
||||
"o": "New Scotland Yard",
|
||||
"sn": "Lestrade"
|
||||
}
|
||||
},
|
||||
"mary": {
|
||||
"dn": "uid=mary,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Mary Watson",
|
||||
"givenName": "Mary",
|
||||
"mail": "mary@bakerstreet.invalid",
|
||||
"sn": "Watson"
|
||||
}
|
||||
},
|
||||
"molly": {
|
||||
"dn": "uid=molly,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Molly Hooper",
|
||||
"givenName": "Molly",
|
||||
"mail": "molly@bakerstreet.invalid",
|
||||
"o": "St. Bartholomew's Hospital",
|
||||
"sn": "Hooper"
|
||||
}
|
||||
},
|
||||
"moriarty": {
|
||||
"dn": "uid=moriarty,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Jim Moriarty",
|
||||
"givenName": "Jim",
|
||||
"mail": "moriarty@bakerstreet.invalid",
|
||||
"sn": "Moriarty"
|
||||
}
|
||||
},
|
||||
"mrs_hudson": {
|
||||
"dn": "uid=mrs_hudson,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Mrs Hudson",
|
||||
"givenName": "Martha",
|
||||
"mail": "mrs_hudson@bakerstreet.invalid",
|
||||
"sn": "Hudson"
|
||||
}
|
||||
},
|
||||
"mycroft": {
|
||||
"dn": "uid=mycroft,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Mycroft Holmes",
|
||||
"givenName": "Mycroft",
|
||||
"mail": "mycroft@bakerstreet.invalid",
|
||||
"sn": "Holmes"
|
||||
}
|
||||
},
|
||||
"sherlock": {
|
||||
"dn": "uid=sherlock,dc=bakerstreet,dc=invalid",
|
||||
"attributes": {
|
||||
"objectClass": "person",
|
||||
"cn": "Sherlock Holmes",
|
||||
"givenName": "Sherlock",
|
||||
"mail": "sherlock@bakerstreet.invalid",
|
||||
"sn": "Holmes"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* 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/. */
|
||||
|
||||
const { LDAPServer } = ChromeUtils.import(
|
||||
"resource://testing-common/LDAPServer.jsm"
|
||||
);
|
||||
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
const autocompleteService = Cc[
|
||||
"@mozilla.org/autocomplete/search;1?name=addrbook"
|
||||
].getService(Ci.nsIAutoCompleteSearch);
|
||||
const jsonFile = do_get_file("data/ldap_contacts.json");
|
||||
const replicationService = Cc[
|
||||
"@mozilla.org/addressbook/ldap-replication-service;1"
|
||||
].getService(Ci.nsIAbLDAPReplicationService);
|
||||
|
||||
add_task(async () => {
|
||||
LDAPServer.open();
|
||||
let contents = await OS.File.read(jsonFile.path);
|
||||
let ldapContacts = await JSON.parse(new TextDecoder().decode(contents));
|
||||
|
||||
let bookPref = MailServices.ab.newAddressBook(
|
||||
"XPCShell",
|
||||
`ldap://localhost:${LDAPServer.port}/people??sub?(objectclass=*)`,
|
||||
0
|
||||
);
|
||||
let book = MailServices.ab.getDirectoryFromId(bookPref);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
equal(book.replicationFileName, "ldap.sqlite");
|
||||
|
||||
Services.prefs.setCharPref("ldap_2.autoComplete.directoryServer", bookPref);
|
||||
Services.prefs.setBoolPref("ldap_2.autoComplete.useDirectory", true);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
LDAPServer.close();
|
||||
});
|
||||
|
||||
let progressResolve;
|
||||
let progressPromise = new Promise(resolve => (progressResolve = resolve));
|
||||
let progressListener = {
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
||||
info("replication started");
|
||||
}
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
info("replication ended");
|
||||
progressResolve();
|
||||
}
|
||||
},
|
||||
onProgressChange(
|
||||
webProgress,
|
||||
request,
|
||||
currentSelfProgress,
|
||||
maxSelfProgress,
|
||||
currentTotalProgress,
|
||||
maxTotalProgress
|
||||
) {},
|
||||
onLocationChange(webProgress, request, location, flags) {},
|
||||
onStatusChange(webProgress, request, status, message) {},
|
||||
onSecurityChange(webProgress, request, state) {},
|
||||
onContentBlockingEvent(webProgress, request, event) {},
|
||||
};
|
||||
|
||||
replicationService.startReplication(book, progressListener);
|
||||
|
||||
await LDAPServer.read(); // BindRequest
|
||||
LDAPServer.writeBindResponse();
|
||||
|
||||
await LDAPServer.read(); // SearchRequest
|
||||
for (let contact of Object.values(ldapContacts)) {
|
||||
LDAPServer.writeSearchResultEntry(contact);
|
||||
}
|
||||
LDAPServer.writeSearchResultDone();
|
||||
|
||||
await progressPromise;
|
||||
equal(book.replicationFileName, "ldap.sqlite");
|
||||
|
||||
Services.io.offline = true;
|
||||
|
||||
let cards = [...book.childCards];
|
||||
deepEqual(cards.map(c => c.displayName).sort(), [
|
||||
"Eurus Holmes",
|
||||
"Greg Lestrade",
|
||||
"Irene Adler",
|
||||
"Jim Moriarty",
|
||||
"John Watson",
|
||||
"Mary Watson",
|
||||
"Molly Hooper",
|
||||
"Mrs Hudson",
|
||||
"Mycroft Holmes",
|
||||
"Sherlock Holmes",
|
||||
]);
|
||||
|
||||
await new Promise(resolve => {
|
||||
autocompleteService.startSearch("molly", '{"type":"addr_to"}', null, {
|
||||
onSearchResult(search, result) {
|
||||
equal(result.matchCount, 1);
|
||||
equal(result.getValueAt(0), "Molly Hooper <molly@bakerstreet.invalid>");
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
await new Promise(resolve => {
|
||||
autocompleteService.startSearch("watson", '{"type":"addr_to"}', null, {
|
||||
onSearchResult(search, result) {
|
||||
equal(result.matchCount, 2);
|
||||
equal(result.getValueAt(0), "John Watson <john@bakerstreet.invalid>");
|
||||
equal(result.getValueAt(1), "Mary Watson <mary@bakerstreet.invalid>");
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Do it again with different information from the server. Ensure we have the new information.
|
||||
|
||||
progressPromise = new Promise(resolve => (progressResolve = resolve));
|
||||
replicationService.startReplication(book, progressListener);
|
||||
|
||||
await LDAPServer.read(); // BindRequest
|
||||
LDAPServer.writeBindResponse();
|
||||
|
||||
await LDAPServer.read(); // SearchRequest
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.eurus);
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.mary);
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.molly);
|
||||
LDAPServer.writeSearchResultDone();
|
||||
|
||||
await progressPromise;
|
||||
equal(book.replicationFileName, "ldap.sqlite");
|
||||
|
||||
cards = [...book.childCards];
|
||||
deepEqual(cards.map(c => c.displayName).sort(), [
|
||||
"Eurus Holmes",
|
||||
"Mary Watson",
|
||||
"Molly Hooper",
|
||||
]);
|
||||
|
||||
// Do it again but cancel. Ensure we still have the old information.
|
||||
|
||||
progressPromise = new Promise(resolve => (progressResolve = resolve));
|
||||
replicationService.startReplication(book, progressListener);
|
||||
|
||||
await LDAPServer.read(); // BindRequest
|
||||
LDAPServer.writeBindResponse();
|
||||
|
||||
await LDAPServer.read(); // SearchRequest
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.john);
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.sherlock);
|
||||
LDAPServer.writeSearchResultEntry(ldapContacts.mrs_hudson);
|
||||
replicationService.cancelReplication(book);
|
||||
|
||||
await progressPromise;
|
||||
|
||||
cards = [...book.childCards];
|
||||
deepEqual(cards.map(c => c.displayName).sort(), [
|
||||
"Eurus Holmes",
|
||||
"Mary Watson",
|
||||
"Molly Hooper",
|
||||
]);
|
||||
});
|
|
@ -17,6 +17,8 @@ support-files = data/*
|
|||
[test_ldap1.js]
|
||||
[test_ldap2.js]
|
||||
[test_ldapOffline.js]
|
||||
[test_ldapReplication.js]
|
||||
skip-if = debug # Fails for unknown reasons.
|
||||
[test_mailList1.js]
|
||||
[test_notifications.js]
|
||||
[test_nsAbAutoCompleteMyDomain.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче