2016-05-13 18:59:46 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
describe("Connector Server", function () {
|
|
|
|
Components.utils.import("resource://zotero-unit/httpd.js");
|
|
|
|
var win, connectorServerPath, testServerPath, httpd;
|
|
|
|
var testServerPort = 16213;
|
|
|
|
|
|
|
|
before(function* () {
|
2016-11-30 11:53:58 +00:00
|
|
|
this.timeout(20000);
|
2016-05-13 18:59:46 +00:00
|
|
|
Zotero.Prefs.set("httpServer.enabled", true);
|
|
|
|
yield resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
2016-11-30 11:53:58 +00:00
|
|
|
yield Zotero.Translators.init();
|
2016-05-13 18:59:46 +00:00
|
|
|
|
|
|
|
win = yield loadZoteroPane();
|
|
|
|
connectorServerPath = 'http://127.0.0.1:' + Zotero.Prefs.get('httpServer.port');
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(function () {
|
2016-06-23 13:15:48 +00:00
|
|
|
// Alternate ports to prevent exceptions not catchable in JS
|
|
|
|
testServerPort += (testServerPort & 1) ? 1 : -1;
|
|
|
|
testServerPath = 'http://127.0.0.1:' + testServerPort;
|
2016-05-13 18:59:46 +00:00
|
|
|
httpd = new HttpServer();
|
|
|
|
httpd.start(testServerPort);
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(function* () {
|
|
|
|
var defer = new Zotero.Promise.defer();
|
|
|
|
httpd.stop(() => defer.resolve());
|
|
|
|
yield defer.promise;
|
|
|
|
});
|
|
|
|
|
2016-05-23 05:19:44 +00:00
|
|
|
after(function () {
|
|
|
|
win.close();
|
|
|
|
});
|
2016-09-18 09:24:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
describe('/connector/getTranslatorCode', function() {
|
|
|
|
it('should respond with translator code', function* () {
|
|
|
|
var code = 'function detectWeb() {}\nfunction doImport() {}';
|
|
|
|
var translator = buildDummyTranslator(4, code);
|
|
|
|
sinon.stub(Zotero.Translators, 'get').returns(translator);
|
|
|
|
|
|
|
|
var response = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/getTranslatorCode",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
translatorID: "dummy-translator",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.isTrue(Zotero.Translators.get.calledWith('dummy-translator'));
|
2016-12-12 12:29:59 +00:00
|
|
|
let translatorCode = yield translator.getCode();
|
|
|
|
assert.equal(response.response, translatorCode);
|
2016-09-18 09:24:55 +00:00
|
|
|
|
|
|
|
Zotero.Translators.get.restore();
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2016-05-23 05:19:44 +00:00
|
|
|
|
2016-12-12 12:29:59 +00:00
|
|
|
describe("/connector/detect", function() {
|
|
|
|
it("should return relevant translators with proxies", function* () {
|
|
|
|
var code = 'function detectWeb() {return "newspaperArticle";}\nfunction doWeb() {}';
|
|
|
|
var translator = buildDummyTranslator("web", code, {target: "https://www.example.com/.*"});
|
|
|
|
sinon.stub(Zotero.Translators, 'getAllForType').resolves([translator]);
|
|
|
|
|
|
|
|
var response = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/detect",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
uri: "https://www-example-com.proxy.example.com/article",
|
|
|
|
html: "<head><title>Owl</title></head><body><p>🦉</p></body>"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(JSON.parse(response.response)[0].proxy.scheme, 'https://%h.proxy.example.com/%p');
|
|
|
|
|
|
|
|
Zotero.Translators.getAllForType.restore();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2016-05-13 18:59:46 +00:00
|
|
|
describe("/connector/saveItems", function () {
|
|
|
|
// TODO: Test cookies
|
2016-05-20 19:51:54 +00:00
|
|
|
it("should save a translated item to the current selected collection", function* () {
|
2016-05-13 18:59:46 +00:00
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var body = {
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: "newspaperArticle",
|
|
|
|
title: "Title",
|
|
|
|
creators: [
|
|
|
|
{
|
|
|
|
firstName: "First",
|
|
|
|
lastName: "Last",
|
|
|
|
creatorType: "author"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
attachments: [
|
|
|
|
{
|
|
|
|
title: "Attachment",
|
|
|
|
url: `${testServerPath}/attachment`,
|
|
|
|
mimeType: "text/html"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: "http://example.com"
|
|
|
|
};
|
|
|
|
|
|
|
|
httpd.registerPathHandler(
|
|
|
|
"/attachment",
|
|
|
|
{
|
|
|
|
handle: function (request, response) {
|
|
|
|
response.setStatusLine(null, 200, "OK");
|
|
|
|
response.write("<html><head><title>Title</title><body>Body</body></html>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var promise = waitForItemEvent('add');
|
2017-02-09 07:17:09 +00:00
|
|
|
var reqPromise = Zotero.HTTP.request(
|
2016-05-13 18:59:46 +00:00
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Check parent item
|
|
|
|
var ids = yield promise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'newspaperArticle');
|
|
|
|
assert.isTrue(collection.hasItem(item.id));
|
|
|
|
|
|
|
|
// Check attachment
|
|
|
|
promise = waitForItemEvent('add');
|
|
|
|
ids = yield promise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(item.isImportedAttachment());
|
|
|
|
|
2017-02-09 07:17:09 +00:00
|
|
|
var req = yield reqPromise;
|
|
|
|
assert.equal(req.status, 201);
|
2016-05-13 18:59:46 +00:00
|
|
|
});
|
2016-06-09 06:44:47 +00:00
|
|
|
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
it("should switch to My Library if read-only library is selected", function* () {
|
2016-06-09 06:44:47 +00:00
|
|
|
var group = yield createGroup({
|
|
|
|
editable: false
|
|
|
|
});
|
|
|
|
yield selectLibrary(win, group.libraryID);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var body = {
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: "newspaperArticle",
|
|
|
|
title: "Title",
|
|
|
|
creators: [
|
|
|
|
{
|
|
|
|
firstName: "First",
|
|
|
|
lastName: "Last",
|
|
|
|
creatorType: "author"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
attachments: []
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: "http://example.com"
|
|
|
|
};
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
var promise = waitForItemEvent('add');
|
|
|
|
var reqPromise = Zotero.HTTP.request(
|
2016-06-09 06:44:47 +00:00
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
2017-07-19 08:55:41 +00:00
|
|
|
body: JSON.stringify(body),
|
|
|
|
successCodes: false
|
2016-06-09 06:44:47 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
// My Library be selected, and the item should be in it
|
|
|
|
var ids = yield promise;
|
2016-06-09 06:44:47 +00:00
|
|
|
assert.equal(
|
2018-04-27 22:27:06 +00:00
|
|
|
win.ZoteroPane.collectionsView.getSelectedLibraryID(),
|
|
|
|
Zotero.Libraries.userLibraryID
|
2016-06-09 06:44:47 +00:00
|
|
|
);
|
2018-04-27 22:27:06 +00:00
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(item.libraryID, Zotero.Libraries.userLibraryID);
|
|
|
|
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'newspaperArticle');
|
|
|
|
|
|
|
|
var req = yield reqPromise;
|
|
|
|
assert.equal(req.status, 201);
|
2016-06-09 06:44:47 +00:00
|
|
|
});
|
2016-12-12 12:29:59 +00:00
|
|
|
|
|
|
|
it("should use the provided proxy to deproxify item url", function* () {
|
|
|
|
yield selectLibrary(win, Zotero.Libraries.userLibraryID);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var body = {
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: "newspaperArticle",
|
|
|
|
title: "Title",
|
|
|
|
creators: [
|
|
|
|
{
|
|
|
|
firstName: "First",
|
|
|
|
lastName: "Last",
|
|
|
|
creatorType: "author"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
attachments: [],
|
|
|
|
url: "https://www-example-com.proxy.example.com/path"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: "https://www-example-com.proxy.example.com/path",
|
|
|
|
proxy: {scheme: 'https://%h.proxy.example.com/%p', dotsToHyphens: true}
|
|
|
|
};
|
|
|
|
|
|
|
|
var promise = waitForItemEvent('add');
|
|
|
|
var req = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Check item
|
|
|
|
var ids = yield promise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(item.getField('url'), 'https://www.example.com/path');
|
|
|
|
});
|
Automatically download open-access PDFs when saving via the connector
If there's no translated PDF or the translated PDF fails and the item
has a DOI, check Zotero's Unpaywall mirror for possible sources and try
to download one of those.
Unlike with "Add Item by Identifier" and "Find Available PDF" in the
item context menu, this does not try the DOI/URL page, since it would
result in more data leakage and most of the time you'd be saving from
the DOI page already. We could consider offering it as an option, but
for it to be useful, you'd have to have an institutional subscription,
be on-campus or connected via VPN (for now), and be saving from
somewhere other than the main page.
A new connector endpoint, sessionProgress, takes the place of
attachmentProgress. Unlike attachmentProgress, sessionProgress can show
new attachments that have been added to the save, and with a little more
work should also be able to show when a parent item has been recognized
for a directly saved PDF.
This also adds support for custom PDF resolvers, available to all PDF
retrieval methods. I'll document those separately.
Closes #1542
2018-08-15 07:34:28 +00:00
|
|
|
|
|
|
|
it("shouldn't return an attachment that isn't being saved", async function () {
|
|
|
|
Zotero.Prefs.set('automaticSnapshots', false);
|
|
|
|
|
|
|
|
await selectLibrary(win, Zotero.Libraries.userLibraryID);
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var body = {
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: "webpage",
|
|
|
|
title: "Title",
|
|
|
|
creators: [],
|
|
|
|
attachments: [
|
|
|
|
{
|
|
|
|
url: "http://example.com/",
|
|
|
|
mimeType: "text/html"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
url: "http://example.com/"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: "http://example.com/"
|
|
|
|
};
|
|
|
|
|
|
|
|
var req = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify(body),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
Zotero.Prefs.clear('automaticSnapshots');
|
|
|
|
|
|
|
|
assert.equal(req.status, 201);
|
|
|
|
assert.lengthOf(req.response.items, 1);
|
|
|
|
assert.lengthOf(req.response.items[0].attachments, 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("PDF retrieval", function () {
|
|
|
|
var oaDOI = '10.1111/abcd';
|
|
|
|
var nonOADOI = '10.2222/bcde';
|
|
|
|
var pdfURL;
|
|
|
|
var badPDFURL;
|
|
|
|
var stub;
|
|
|
|
|
|
|
|
before(function () {
|
|
|
|
var origFunc = Zotero.HTTP.request.bind(Zotero.HTTP);
|
|
|
|
stub = sinon.stub(Zotero.HTTP, 'request');
|
|
|
|
stub.callsFake(function (method, url, options) {
|
|
|
|
// OA PDF lookup
|
|
|
|
if (url.startsWith(ZOTERO_CONFIG.SERVICES_URL)) {
|
|
|
|
let json = JSON.parse(options.body);
|
|
|
|
let response = [];
|
|
|
|
if (json.doi == oaDOI) {
|
|
|
|
response.push({
|
|
|
|
url: pdfURL,
|
|
|
|
version: 'submittedVersion'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
status: 200,
|
|
|
|
response
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return origFunc(...arguments);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
pdfURL = testServerPath + '/pdf';
|
|
|
|
badPDFURL = testServerPath + '/badpdf';
|
|
|
|
|
|
|
|
httpd.registerFile(
|
|
|
|
pdfURL.substr(testServerPath.length),
|
|
|
|
Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'test.pdf'))
|
|
|
|
);
|
|
|
|
// PDF URL that's actually an HTML page
|
|
|
|
httpd.registerFile(
|
|
|
|
badPDFURL.substr(testServerPath.length),
|
|
|
|
Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'test.html'))
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
stub.resetHistory();
|
|
|
|
});
|
|
|
|
|
|
|
|
after(() => {
|
|
|
|
stub.restore();
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("should download a translated PDF", async function () {
|
|
|
|
var collection = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
// Save item
|
|
|
|
var itemAddPromise = waitForItemEvent('add');
|
|
|
|
var saveItemsReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: 'journalArticle',
|
|
|
|
title: 'Title',
|
|
|
|
DOI: nonOADOI,
|
|
|
|
attachments: [
|
|
|
|
{
|
|
|
|
title: "PDF",
|
|
|
|
url: pdfURL,
|
|
|
|
mimeType: 'application/pdf'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: 'http://website/article'
|
|
|
|
}),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(saveItemsReq.status, 201);
|
|
|
|
assert.lengthOf(saveItemsReq.response.items, 1);
|
|
|
|
// Translated attachment should show up in the initial response
|
|
|
|
assert.lengthOf(saveItemsReq.response.items[0].attachments, 1);
|
|
|
|
assert.notProperty(saveItemsReq.response.items[0], 'DOI');
|
|
|
|
assert.notProperty(saveItemsReq.response.items[0].attachments[0], 'progress');
|
|
|
|
|
|
|
|
// Check parent item
|
|
|
|
var ids = await itemAddPromise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'journalArticle');
|
|
|
|
assert.isTrue(collection.hasItem(item.id));
|
|
|
|
|
|
|
|
// Legacy endpoint should show 0
|
|
|
|
let attachmentProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/attachmentProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify([saveItemsReq.response.items[0].attachments[0].id]),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(attachmentProgressReq.status, 200);
|
|
|
|
let progress = attachmentProgressReq.response;
|
|
|
|
assert.sameOrderedMembers(progress, [0]);
|
|
|
|
|
|
|
|
// Wait for the attachment to finish saving
|
|
|
|
itemAddPromise = waitForItemEvent('add');
|
|
|
|
var i = 0;
|
|
|
|
while (i < 3) {
|
|
|
|
let sessionProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/sessionProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({ sessionID }),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(sessionProgressReq.status, 200);
|
|
|
|
let response = sessionProgressReq.response;
|
|
|
|
assert.lengthOf(response.items, 1);
|
|
|
|
let item = response.items[0];
|
|
|
|
if (item.attachments.length) {
|
|
|
|
let attachments = item.attachments;
|
|
|
|
assert.lengthOf(attachments, 1);
|
|
|
|
let attachment = attachments[0];
|
|
|
|
switch (i) {
|
|
|
|
// Translated PDF in progress
|
|
|
|
case 0:
|
|
|
|
if (attachment.title == "PDF"
|
|
|
|
&& Number.isInteger(attachment.progress)
|
|
|
|
&& attachment.progress < 100) {
|
|
|
|
assert.isFalse(response.done);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Translated PDF finished
|
|
|
|
case 1:
|
|
|
|
if (attachment.title == "PDF" && attachment.progress == 100) {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// done: true
|
|
|
|
case 2:
|
|
|
|
if (response.done) {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await Zotero.Promise.delay(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Legacy endpoint should show 100
|
|
|
|
attachmentProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/attachmentProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify([saveItemsReq.response.items[0].attachments[0].id]),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(attachmentProgressReq.status, 200);
|
|
|
|
progress = attachmentProgressReq.response;
|
|
|
|
assert.sameOrderedMembers(progress, [100]);
|
|
|
|
|
|
|
|
// Check attachment
|
|
|
|
var ids = await itemAddPromise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(item.isImportedAttachment());
|
|
|
|
assert.equal(item.getField('title'), 'PDF');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("should download open-access PDF if no PDF provided", async function () {
|
|
|
|
var collection = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
// Save item
|
|
|
|
var itemAddPromise = waitForItemEvent('add');
|
|
|
|
var saveItemsReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: 'journalArticle',
|
|
|
|
title: 'Title',
|
|
|
|
DOI: oaDOI,
|
|
|
|
attachments: []
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: 'http://website/article'
|
|
|
|
}),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(saveItemsReq.status, 201);
|
|
|
|
assert.lengthOf(saveItemsReq.response.items, 1);
|
|
|
|
// Attachment shouldn't show up in the initial response
|
|
|
|
assert.lengthOf(saveItemsReq.response.items[0].attachments, 0);
|
|
|
|
|
|
|
|
// Check parent item
|
|
|
|
var ids = await itemAddPromise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'journalArticle');
|
|
|
|
assert.isTrue(collection.hasItem(item.id));
|
|
|
|
|
|
|
|
// Wait for the attachment to finish saving
|
|
|
|
itemAddPromise = waitForItemEvent('add');
|
|
|
|
var wasZero = false;
|
|
|
|
var was100 = false;
|
|
|
|
while (true) {
|
|
|
|
let sessionProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/sessionProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({ sessionID }),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(sessionProgressReq.status, 200);
|
|
|
|
let response = sessionProgressReq.response;
|
|
|
|
assert.typeOf(response.items, 'array');
|
|
|
|
assert.lengthOf(response.items, 1);
|
|
|
|
let item = response.items[0];
|
|
|
|
if (item.attachments.length) {
|
|
|
|
// 'progress' should have started at 0
|
|
|
|
if (item.attachments[0].progress === 0) {
|
|
|
|
wasZero = true;
|
|
|
|
}
|
|
|
|
else if (!was100 && item.attachments[0].progress == 100) {
|
|
|
|
if (response.done) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
was100 = true;
|
|
|
|
}
|
|
|
|
else if (response.done) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.isFalse(response.done);
|
|
|
|
await Zotero.Promise.delay(10);
|
|
|
|
}
|
|
|
|
assert.isTrue(wasZero);
|
|
|
|
|
|
|
|
// Check attachment
|
|
|
|
var ids = await itemAddPromise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(item.isImportedAttachment());
|
2018-08-24 23:41:23 +00:00
|
|
|
assert.equal(item.getField('title'), Zotero.getString('attachment.submittedVersion'));
|
Automatically download open-access PDFs when saving via the connector
If there's no translated PDF or the translated PDF fails and the item
has a DOI, check Zotero's Unpaywall mirror for possible sources and try
to download one of those.
Unlike with "Add Item by Identifier" and "Find Available PDF" in the
item context menu, this does not try the DOI/URL page, since it would
result in more data leakage and most of the time you'd be saving from
the DOI page already. We could consider offering it as an option, but
for it to be useful, you'd have to have an institutional subscription,
be on-campus or connected via VPN (for now), and be saving from
somewhere other than the main page.
A new connector endpoint, sessionProgress, takes the place of
attachmentProgress. Unlike attachmentProgress, sessionProgress can show
new attachments that have been added to the save, and with a little more
work should also be able to show when a parent item has been recognized
for a directly saved PDF.
This also adds support for custom PDF resolvers, available to all PDF
retrieval methods. I'll document those separately.
Closes #1542
2018-08-15 07:34:28 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it("should download open-access PDF if a translated PDF fails", async function () {
|
|
|
|
var collection = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
// Save item
|
|
|
|
var itemAddPromise = waitForItemEvent('add');
|
|
|
|
var saveItemsReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: 'journalArticle',
|
|
|
|
title: 'Title',
|
|
|
|
DOI: oaDOI,
|
|
|
|
attachments: [
|
|
|
|
{
|
|
|
|
title: "PDF",
|
|
|
|
url: badPDFURL,
|
|
|
|
mimeType: 'application/pdf'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: 'http://website/article'
|
|
|
|
}),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(saveItemsReq.status, 201);
|
|
|
|
assert.lengthOf(saveItemsReq.response.items, 1);
|
|
|
|
// Translated attachment should show up in the initial response
|
|
|
|
assert.lengthOf(saveItemsReq.response.items[0].attachments, 1);
|
|
|
|
assert.notProperty(saveItemsReq.response.items[0], 'DOI');
|
|
|
|
assert.notProperty(saveItemsReq.response.items[0].attachments[0], 'progress');
|
|
|
|
|
|
|
|
// Check parent item
|
|
|
|
var ids = await itemAddPromise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'journalArticle');
|
|
|
|
assert.isTrue(collection.hasItem(item.id));
|
|
|
|
|
|
|
|
// Legacy endpoint should show 0
|
|
|
|
let attachmentProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/attachmentProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify([saveItemsReq.response.items[0].attachments[0].id]),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(attachmentProgressReq.status, 200);
|
|
|
|
let progress = attachmentProgressReq.response;
|
|
|
|
assert.sameOrderedMembers(progress, [0]);
|
|
|
|
|
|
|
|
// Wait for the attachment to finish saving
|
|
|
|
itemAddPromise = waitForItemEvent('add');
|
|
|
|
var i = 0;
|
|
|
|
while (i < 4) {
|
|
|
|
let sessionProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/sessionProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({ sessionID }),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(sessionProgressReq.status, 200);
|
|
|
|
let response = sessionProgressReq.response;
|
|
|
|
assert.lengthOf(response.items, 1);
|
|
|
|
let item = response.items[0];
|
|
|
|
if (item.attachments.length) {
|
|
|
|
let attachments = item.attachments;
|
|
|
|
assert.lengthOf(attachments, 1);
|
|
|
|
let attachment = attachments[0];
|
|
|
|
switch (i) {
|
|
|
|
// Translated PDF in progress
|
|
|
|
case 0:
|
|
|
|
if (attachment.title == "PDF"
|
|
|
|
&& Number.isInteger(attachment.progress)
|
|
|
|
&& attachment.progress < 100) {
|
|
|
|
assert.isFalse(response.done);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// OA PDF in progress
|
|
|
|
case 1:
|
|
|
|
if (attachment.title == Zotero.getString('findPDF.openAccessPDF')
|
|
|
|
&& Number.isInteger(attachment.progress)
|
|
|
|
&& attachment.progress < 100) {
|
|
|
|
assert.isFalse(response.done);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// OA PDF finished
|
|
|
|
case 2:
|
|
|
|
if (attachment.progress === 100) {
|
|
|
|
assert.equal(attachment.title, Zotero.getString('findPDF.openAccessPDF'));
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// done: true
|
|
|
|
case 3:
|
|
|
|
if (response.done) {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await Zotero.Promise.delay(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Legacy endpoint should show 100
|
|
|
|
attachmentProgressReq = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/attachmentProgress",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify([saveItemsReq.response.items[0].attachments[0].id]),
|
|
|
|
responseType: 'json'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(attachmentProgressReq.status, 200);
|
|
|
|
progress = attachmentProgressReq.response;
|
|
|
|
assert.sameOrderedMembers(progress, [100]);
|
|
|
|
|
|
|
|
// Check attachment
|
|
|
|
var ids = await itemAddPromise;
|
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(item.isImportedAttachment());
|
2018-08-24 23:41:23 +00:00
|
|
|
assert.equal(item.getField('title'), Zotero.getString('attachment.submittedVersion'));
|
Automatically download open-access PDFs when saving via the connector
If there's no translated PDF or the translated PDF fails and the item
has a DOI, check Zotero's Unpaywall mirror for possible sources and try
to download one of those.
Unlike with "Add Item by Identifier" and "Find Available PDF" in the
item context menu, this does not try the DOI/URL page, since it would
result in more data leakage and most of the time you'd be saving from
the DOI page already. We could consider offering it as an option, but
for it to be useful, you'd have to have an institutional subscription,
be on-campus or connected via VPN (for now), and be saving from
somewhere other than the main page.
A new connector endpoint, sessionProgress, takes the place of
attachmentProgress. Unlike attachmentProgress, sessionProgress can show
new attachments that have been added to the save, and with a little more
work should also be able to show when a parent item has been recognized
for a directly saved PDF.
This also adds support for custom PDF resolvers, available to all PDF
retrieval methods. I'll document those separately.
Closes #1542
2018-08-15 07:34:28 +00:00
|
|
|
});
|
|
|
|
});
|
2016-05-13 18:59:46 +00:00
|
|
|
});
|
2016-05-20 19:51:54 +00:00
|
|
|
|
|
|
|
describe("/connector/saveSnapshot", function () {
|
|
|
|
it("should save a webpage item and snapshot to the current selected collection", function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
// saveSnapshot saves parent and child before returning
|
|
|
|
var ids1, ids2;
|
|
|
|
var promise = waitForItemEvent('add').then(function (ids) {
|
|
|
|
ids1 = ids;
|
|
|
|
return waitForItemEvent('add').then(function (ids) {
|
|
|
|
ids2 = ids;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveSnapshot",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
url: "http://example.com",
|
|
|
|
html: "<html><head><title>Title</title><body>Body</body></html>"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.isTrue(promise.isFulfilled());
|
|
|
|
|
|
|
|
// Check parent item
|
|
|
|
assert.lengthOf(ids1, 1);
|
|
|
|
var item = Zotero.Items.get(ids1[0]);
|
|
|
|
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'webpage');
|
|
|
|
assert.isTrue(collection.hasItem(item.id));
|
|
|
|
assert.equal(item.getField('title'), 'Title');
|
|
|
|
|
|
|
|
// Check attachment
|
|
|
|
assert.lengthOf(ids2, 1);
|
|
|
|
item = Zotero.Items.get(ids2[0]);
|
|
|
|
assert.isTrue(item.isImportedAttachment());
|
|
|
|
assert.equal(item.getField('title'), 'Title');
|
|
|
|
});
|
2016-05-25 21:34:26 +00:00
|
|
|
|
2018-03-01 02:43:18 +00:00
|
|
|
it("should save a PDF to the current selected collection and retrieve metadata", async function () {
|
|
|
|
var collection = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
2016-05-25 21:34:26 +00:00
|
|
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
file.append('test.pdf');
|
|
|
|
httpd.registerFile("/test.pdf", file);
|
|
|
|
|
|
|
|
var promise = waitForItemEvent('add');
|
2018-03-01 02:43:18 +00:00
|
|
|
|
|
|
|
var origRequest = Zotero.HTTP.request.bind(Zotero.HTTP);
|
|
|
|
var called = 0;
|
|
|
|
var stub = sinon.stub(Zotero.HTTP, 'request').callsFake(function (method, url, options) {
|
|
|
|
// Forward saveSnapshot request
|
|
|
|
if (url.endsWith('saveSnapshot')) {
|
|
|
|
return origRequest(...arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fake recognizer response
|
|
|
|
return Zotero.Promise.resolve({
|
|
|
|
getResponseHeader: () => {},
|
|
|
|
responseText: JSON.stringify({
|
|
|
|
title: 'Test',
|
|
|
|
authors: []
|
|
|
|
})
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await Zotero.HTTP.request(
|
2016-05-25 21:34:26 +00:00
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveSnapshot",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
url: testServerPath + "/test.pdf",
|
|
|
|
pdf: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-03-01 02:43:18 +00:00
|
|
|
var ids = await promise;
|
2016-05-25 21:34:26 +00:00
|
|
|
|
|
|
|
assert.lengthOf(ids, 1);
|
2018-10-09 23:02:46 +00:00
|
|
|
var attachment = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(attachment.isImportedAttachment());
|
|
|
|
assert.equal(attachment.attachmentContentType, 'application/pdf');
|
|
|
|
assert.isTrue(collection.hasItem(attachment.id));
|
|
|
|
|
|
|
|
await waitForItemEvent('add');
|
|
|
|
await waitForItemEvent('modify');
|
|
|
|
await waitForItemEvent('modify');
|
2018-03-01 02:43:18 +00:00
|
|
|
|
2018-10-09 23:02:46 +00:00
|
|
|
assert.isFalse(attachment.isTopLevelItem());
|
2018-03-01 02:43:18 +00:00
|
|
|
|
|
|
|
stub.restore();
|
2016-05-25 21:34:26 +00:00
|
|
|
});
|
2016-06-09 06:44:47 +00:00
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
it("should switch to My Library if a read-only library is selected", function* () {
|
2016-06-09 06:44:47 +00:00
|
|
|
var group = yield createGroup({
|
|
|
|
editable: false
|
|
|
|
});
|
|
|
|
yield selectLibrary(win, group.libraryID);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
var promise = waitForItemEvent('add');
|
|
|
|
var reqPromise = Zotero.HTTP.request(
|
2016-06-09 06:44:47 +00:00
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveSnapshot",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
url: "http://example.com",
|
|
|
|
html: "<html><head><title>Title</title><body>Body</body></html>"
|
2017-09-27 21:31:17 +00:00
|
|
|
}),
|
|
|
|
successCodes: false
|
2016-06-09 06:44:47 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
// My Library be selected, and the item should be in it
|
|
|
|
var ids = yield promise;
|
2016-06-09 06:44:47 +00:00
|
|
|
assert.equal(
|
2018-04-27 22:27:06 +00:00
|
|
|
win.ZoteroPane.collectionsView.getSelectedLibraryID(),
|
|
|
|
Zotero.Libraries.userLibraryID
|
2016-06-09 06:44:47 +00:00
|
|
|
);
|
2018-04-27 22:27:06 +00:00
|
|
|
assert.lengthOf(ids, 1);
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.equal(item.libraryID, Zotero.Libraries.userLibraryID);
|
|
|
|
|
|
|
|
var req = yield reqPromise;
|
|
|
|
assert.equal(req.status, 201);
|
2016-06-09 06:44:47 +00:00
|
|
|
});
|
2016-05-20 19:51:54 +00:00
|
|
|
});
|
2016-11-29 19:59:58 +00:00
|
|
|
|
2017-02-21 12:26:14 +00:00
|
|
|
describe("/connector/savePage", function() {
|
2017-09-27 21:31:17 +00:00
|
|
|
before(async function () {
|
|
|
|
await selectLibrary(win);
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
});
|
|
|
|
|
2017-02-21 12:26:14 +00:00
|
|
|
it("should return 500 if no translator available for page", function* () {
|
|
|
|
var xmlhttp = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/savePage",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
uri: "http://example.com",
|
|
|
|
html: "<html><head><title>Title</title><body>Body</body></html>"
|
|
|
|
}),
|
|
|
|
successCodes: false
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(xmlhttp.status, 500);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should translate a page if translators are available", function* () {
|
|
|
|
var html = Zotero.File.getContentsFromURL(getTestDataUrl('coins.html'));
|
|
|
|
var promise = waitForItemEvent('add');
|
|
|
|
var xmlhttp = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/savePage",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
uri: "https://example.com/test",
|
|
|
|
html
|
|
|
|
}),
|
|
|
|
successCodes: false
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
let ids = yield promise;
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
var title = "Test Page";
|
|
|
|
assert.equal(JSON.parse(xmlhttp.responseText).items[0].title, title);
|
|
|
|
assert.equal(item.getField('title'), title);
|
|
|
|
assert.equal(xmlhttp.status, 201);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-02-06 06:40:38 +00:00
|
|
|
describe("/connector/updateSession", function () {
|
|
|
|
it("should update collections and tags of item saved via /saveItems", async function () {
|
|
|
|
var collection1 = await createDataObject('collection');
|
|
|
|
var collection2 = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
var body = {
|
|
|
|
sessionID,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: "newspaperArticle",
|
|
|
|
title: "Title",
|
|
|
|
creators: [
|
|
|
|
{
|
|
|
|
firstName: "First",
|
|
|
|
lastName: "Last",
|
|
|
|
creatorType: "author"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
attachments: [
|
|
|
|
{
|
|
|
|
title: "Attachment",
|
|
|
|
url: `${testServerPath}/attachment`,
|
|
|
|
mimeType: "text/html"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: "http://example.com"
|
|
|
|
};
|
|
|
|
|
|
|
|
httpd.registerPathHandler(
|
|
|
|
"/attachment",
|
|
|
|
{
|
|
|
|
handle: function (request, response) {
|
|
|
|
response.setStatusLine(null, 200, "OK");
|
|
|
|
response.write("<html><head><title>Title</title><body>Body</body></html>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids = await waitForItemEvent('add');
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(collection2.hasItem(item.id));
|
|
|
|
await waitForItemEvent('add');
|
|
|
|
|
|
|
|
var req = await reqPromise;
|
|
|
|
assert.equal(req.status, 201);
|
|
|
|
|
|
|
|
// Update saved item
|
|
|
|
var req = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: collection1.treeViewID,
|
|
|
|
tags: "A, B"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isTrue(collection1.hasItem(item.id));
|
|
|
|
assert.isTrue(item.hasTag("A"));
|
|
|
|
assert.isTrue(item.hasTag("B"));
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should update collections and tags of PDF saved via /saveSnapshot", async function () {
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
var collection1 = await createDataObject('collection');
|
|
|
|
var collection2 = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
file.append('test.pdf');
|
|
|
|
httpd.registerFile("/test.pdf", file);
|
|
|
|
|
|
|
|
var ids;
|
|
|
|
var promise = waitForItemEvent('add');
|
|
|
|
var reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveSnapshot",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
url: testServerPath + "/test.pdf",
|
|
|
|
pdf: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids = await promise;
|
|
|
|
var item = Zotero.Items.get(ids[0]);
|
|
|
|
assert.isTrue(collection2.hasItem(item.id));
|
|
|
|
var req = await reqPromise;
|
|
|
|
assert.equal(req.status, 201);
|
|
|
|
|
|
|
|
// Update saved item
|
|
|
|
var req = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: collection1.treeViewID,
|
|
|
|
tags: "A, B"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isTrue(collection1.hasItem(item.id));
|
|
|
|
assert.isTrue(item.hasTag("A"));
|
|
|
|
assert.isTrue(item.hasTag("B"));
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should update collections and tags of webpage saved via /saveSnapshot", async function () {
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
var collection1 = await createDataObject('collection');
|
|
|
|
var collection2 = await createDataObject('collection');
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
// saveSnapshot saves parent and child before returning
|
|
|
|
var ids1, ids2;
|
|
|
|
var promise = waitForItemEvent('add').then(function (ids) {
|
|
|
|
ids1 = ids;
|
|
|
|
return waitForItemEvent('add').then(function (ids) {
|
|
|
|
ids2 = ids;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveSnapshot",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
url: "http://example.com",
|
|
|
|
html: "<html><head><title>Title</title><body>Body</body></html>"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.isTrue(promise.isFulfilled());
|
|
|
|
|
|
|
|
var item = Zotero.Items.get(ids1[0]);
|
|
|
|
|
|
|
|
// Update saved item
|
|
|
|
var req = await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: collection1.treeViewID,
|
|
|
|
tags: "A, B"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isTrue(collection1.hasItem(item.id));
|
|
|
|
assert.isTrue(item.hasTag("A"));
|
|
|
|
assert.isTrue(item.hasTag("B"));
|
|
|
|
});
|
2019-01-07 09:34:14 +00:00
|
|
|
|
|
|
|
it("should move item saved via /saveItems to another library", async function () {
|
|
|
|
var group = await createGroup({ editable: true, filesEditable: false });
|
|
|
|
await selectLibrary(win);
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
var body = {
|
|
|
|
sessionID,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
itemType: "newspaperArticle",
|
|
|
|
title: "Title",
|
|
|
|
attachments: [
|
|
|
|
{
|
|
|
|
title: "Attachment",
|
|
|
|
url: `${testServerPath}/attachment`,
|
|
|
|
mimeType: "text/html"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
uri: "http://example.com"
|
|
|
|
};
|
|
|
|
|
|
|
|
httpd.registerPathHandler(
|
|
|
|
"/attachment",
|
|
|
|
{
|
|
|
|
handle: function (request, response) {
|
|
|
|
response.setStatusLine(null, 200, "OK");
|
|
|
|
response.write("<html><head><title>Title</title><body>Body</body></html>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveItems",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids1 = await waitForItemEvent('add');
|
|
|
|
var item1 = Zotero.Items.get(ids1[0]);
|
2019-01-22 10:25:15 +00:00
|
|
|
// Attachment
|
2019-01-07 09:34:14 +00:00
|
|
|
await waitForItemEvent('add');
|
|
|
|
|
|
|
|
var req = await reqPromise;
|
|
|
|
assert.equal(req.status, 201);
|
|
|
|
|
|
|
|
// Move item to group without file attachment
|
|
|
|
reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: group.treeViewID
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids2 = await waitForItemEvent('add');
|
|
|
|
var item2 = Zotero.Items.get(ids2[0]);
|
|
|
|
|
|
|
|
req = await reqPromise;
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isFalse(Zotero.Items.exists(item1.id));
|
|
|
|
assert.equal(item2.libraryID, group.libraryID);
|
|
|
|
assert.equal(item2.numAttachments(), 0);
|
|
|
|
|
|
|
|
// Move back to My Library and resave attachment
|
|
|
|
reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: Zotero.Libraries.userLibrary.treeViewID
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids3 = await waitForItemEvent('add');
|
|
|
|
var item3 = Zotero.Items.get(ids3[0]);
|
2019-01-22 10:25:15 +00:00
|
|
|
// Attachment
|
2019-01-07 09:34:14 +00:00
|
|
|
await waitForItemEvent('add');
|
|
|
|
|
|
|
|
req = await reqPromise;
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isFalse(Zotero.Items.exists(item2.id));
|
|
|
|
assert.equal(item3.libraryID, Zotero.Libraries.userLibraryID);
|
|
|
|
assert.equal(item3.numAttachments(), 1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should move item saved via /saveSnapshot to another library", async function () {
|
|
|
|
var group = await createGroup({ editable: true, filesEditable: false });
|
|
|
|
await selectLibrary(win);
|
|
|
|
await waitForItemsLoad(win);
|
|
|
|
var sessionID = Zotero.Utilities.randomString();
|
|
|
|
|
|
|
|
// saveSnapshot saves parent and child before returning
|
|
|
|
var ids1;
|
|
|
|
var promise = waitForItemEvent('add').then(function (ids) {
|
|
|
|
ids1 = ids;
|
|
|
|
return waitForItemEvent('add').then(function (ids) {
|
|
|
|
ids1 = ids1.concat(ids);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
await Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/saveSnapshot",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
url: "http://example.com",
|
|
|
|
html: "<html><head><title>Title</title><body>Body</body></html>"
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.isTrue(promise.isFulfilled());
|
|
|
|
|
|
|
|
var item1 = Zotero.Items.get(ids1[0]);
|
|
|
|
|
|
|
|
// Move item to group without file attachment
|
|
|
|
var reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: group.treeViewID
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids2 = await waitForItemEvent('add');
|
|
|
|
var item2 = Zotero.Items.get(ids2[0]);
|
|
|
|
|
|
|
|
var req = await reqPromise;
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isFalse(Zotero.Items.exists(item1.id));
|
|
|
|
assert.equal(item2.libraryID, group.libraryID);
|
|
|
|
assert.equal(item2.numAttachments(), 0);
|
|
|
|
|
|
|
|
// Move back to My Library and resave attachment
|
|
|
|
reqPromise = Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
connectorServerPath + "/connector/updateSession",
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
sessionID,
|
|
|
|
target: Zotero.Libraries.userLibrary.treeViewID
|
|
|
|
})
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var ids3 = await waitForItemEvent('add');
|
|
|
|
var item3 = Zotero.Items.get(ids3[0]);
|
|
|
|
await waitForItemEvent('add');
|
|
|
|
|
|
|
|
req = await reqPromise;
|
|
|
|
assert.equal(req.status, 200);
|
|
|
|
assert.isFalse(Zotero.Items.exists(item2.id));
|
|
|
|
assert.equal(item3.libraryID, Zotero.Libraries.userLibraryID);
|
|
|
|
assert.equal(item3.numAttachments(), 1);
|
|
|
|
});
|
2018-02-06 06:40:38 +00:00
|
|
|
});
|
|
|
|
|
2016-11-30 11:53:58 +00:00
|
|
|
describe('/connector/installStyle', function() {
|
2016-11-29 19:59:58 +00:00
|
|
|
var endpoint;
|
|
|
|
|
|
|
|
before(function() {
|
2016-11-30 11:53:58 +00:00
|
|
|
endpoint = connectorServerPath + "/connector/installStyle";
|
2016-11-29 19:59:58 +00:00
|
|
|
});
|
|
|
|
|
2016-11-30 11:53:58 +00:00
|
|
|
it('should reject styles with invalid text', function* () {
|
|
|
|
var error = yield getPromiseError(Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
endpoint,
|
|
|
|
{
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
body: '{}'
|
|
|
|
}
|
|
|
|
));
|
|
|
|
assert.instanceOf(error, Zotero.HTTP.UnexpectedStatusException);
|
|
|
|
assert.equal(error.xmlhttp.status, 400);
|
2016-12-01 03:40:54 +00:00
|
|
|
assert.equal(error.xmlhttp.responseText, Zotero.getString("styles.installError", "(null)"));
|
2016-11-29 19:59:58 +00:00
|
|
|
});
|
|
|
|
|
2016-11-30 11:53:58 +00:00
|
|
|
it('should import a style with application/vnd.citationstyles.style+xml content-type', function* () {
|
2017-05-31 15:44:35 +00:00
|
|
|
sinon.stub(Zotero.Styles, 'install').callsFake(function(style) {
|
2016-11-29 19:59:58 +00:00
|
|
|
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
|
|
|
.createInstance(Components.interfaces.nsIDOMParser),
|
|
|
|
doc = parser.parseFromString(style, "application/xml");
|
|
|
|
|
2018-08-13 22:16:43 +00:00
|
|
|
return Zotero.Promise.resolve({
|
|
|
|
styleTitle: Zotero.Utilities.xpathText(
|
|
|
|
doc, '/csl:style/csl:info[1]/csl:title[1]', Zotero.Styles.ns
|
|
|
|
),
|
|
|
|
styleID: Zotero.Utilities.xpathText(
|
|
|
|
doc, '/csl:style/csl:info[1]/csl:id[1]', Zotero.Styles.ns
|
|
|
|
)
|
|
|
|
});
|
2016-11-29 19:59:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
var style = `<?xml version="1.0" encoding="utf-8"?>
|
|
|
|
<style xmlns="http://purl.org/net/xbiblio/csl" version="1.0" default-locale="de-DE">
|
|
|
|
<info>
|
|
|
|
<title>Test1</title>
|
|
|
|
<id>http://www.example.com/test2</id>
|
|
|
|
<link href="http://www.zotero.org/styles/cell" rel="independent-parent"/>
|
|
|
|
</info>
|
|
|
|
</style>
|
|
|
|
`;
|
|
|
|
var response = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
endpoint,
|
|
|
|
{
|
2016-11-30 11:53:58 +00:00
|
|
|
headers: { "Content-Type": "application/vnd.citationstyles.style+xml" },
|
2016-11-29 19:59:58 +00:00
|
|
|
body: style
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert.equal(response.status, 201);
|
|
|
|
assert.equal(response.response, JSON.stringify({name: 'Test1'}));
|
|
|
|
Zotero.Styles.install.restore();
|
|
|
|
});
|
|
|
|
});
|
2016-11-30 11:53:58 +00:00
|
|
|
|
|
|
|
describe('/connector/import', function() {
|
|
|
|
var endpoint;
|
|
|
|
|
|
|
|
before(function() {
|
|
|
|
endpoint = connectorServerPath + "/connector/import";
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should reject resources that do not contain import data', function* () {
|
|
|
|
var error = yield getPromiseError(Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
endpoint,
|
|
|
|
{
|
|
|
|
headers: { "Content-Type": "text/plain" },
|
|
|
|
body: 'Owl'
|
|
|
|
}
|
|
|
|
));
|
|
|
|
assert.instanceOf(error, Zotero.HTTP.UnexpectedStatusException);
|
|
|
|
assert.equal(error.xmlhttp.status, 400);
|
|
|
|
});
|
|
|
|
|
2017-08-15 08:14:50 +00:00
|
|
|
it('should import resources (BibTeX) into selected collection', function* () {
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
2016-11-30 11:53:58 +00:00
|
|
|
var resource = `@book{test1,
|
|
|
|
title={Test1},
|
|
|
|
author={Owl},
|
|
|
|
year={1000},
|
2018-05-24 00:39:32 +00:00
|
|
|
publisher={Curly Braces Publishing},
|
|
|
|
keywords={A, B}
|
2016-11-30 11:53:58 +00:00
|
|
|
}`;
|
2018-04-27 22:27:06 +00:00
|
|
|
|
|
|
|
var addedItemIDsPromise = waitForItemEvent('add');
|
|
|
|
var req = yield Zotero.HTTP.request(
|
2016-11-30 11:53:58 +00:00
|
|
|
'POST',
|
|
|
|
endpoint,
|
|
|
|
{
|
|
|
|
headers: { "Content-Type": "application/x-bibtex" },
|
|
|
|
body: resource
|
|
|
|
}
|
|
|
|
);
|
2018-04-27 22:27:06 +00:00
|
|
|
assert.equal(req.status, 201);
|
|
|
|
assert.equal(JSON.parse(req.responseText)[0].title, 'Test1');
|
2017-08-15 08:14:50 +00:00
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
let itemIDs = yield addedItemIDsPromise;
|
|
|
|
assert.isTrue(collection.hasItem(itemIDs[0]));
|
2018-05-24 00:39:32 +00:00
|
|
|
var item = Zotero.Items.get(itemIDs[0]);
|
|
|
|
assert.sameDeepMembers(item.getTags(), [{ tag: 'A', type: 1 }, { tag: 'B', type: 1 }]);
|
2016-11-30 11:53:58 +00:00
|
|
|
});
|
2017-09-27 21:31:17 +00:00
|
|
|
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
it('should switch to My Library if read-only library is selected', function* () {
|
2017-09-27 21:31:17 +00:00
|
|
|
var group = yield createGroup({
|
|
|
|
editable: false
|
|
|
|
});
|
|
|
|
yield selectLibrary(win, group.libraryID);
|
|
|
|
yield waitForItemsLoad(win);
|
|
|
|
|
|
|
|
var resource = `@book{test1,
|
|
|
|
title={Test1},
|
|
|
|
author={Owl},
|
|
|
|
year={1000},
|
|
|
|
publisher={Curly Braces Publishing}
|
|
|
|
}`;
|
2018-04-27 22:27:06 +00:00
|
|
|
|
|
|
|
var addedItemIDsPromise = waitForItemEvent('add');
|
2017-09-27 21:31:17 +00:00
|
|
|
var req = yield Zotero.HTTP.request(
|
|
|
|
'POST',
|
|
|
|
endpoint,
|
|
|
|
{
|
|
|
|
headers: { "Content-Type": "application/x-bibtex" },
|
|
|
|
body: resource,
|
|
|
|
successCodes: false
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2018-04-27 22:27:06 +00:00
|
|
|
assert.equal(req.status, 201);
|
2017-09-27 21:31:17 +00:00
|
|
|
assert.equal(
|
2018-04-27 22:27:06 +00:00
|
|
|
win.ZoteroPane.collectionsView.getSelectedLibraryID(),
|
|
|
|
Zotero.Libraries.userLibraryID
|
2017-09-27 21:31:17 +00:00
|
|
|
);
|
2018-04-27 22:27:06 +00:00
|
|
|
|
|
|
|
let itemIDs = yield addedItemIDsPromise;
|
|
|
|
var item = Zotero.Items.get(itemIDs[0]);
|
|
|
|
assert.equal(item.libraryID, Zotero.Libraries.userLibraryID);
|
2017-09-27 21:31:17 +00:00
|
|
|
});
|
2016-11-30 11:53:58 +00:00
|
|
|
});
|
2016-05-13 18:59:46 +00:00
|
|
|
});
|