zotero/test/tests/preferences_syncTest.js
Dan Stillman 322b764091 Show email addresses in sync settings
Fetch emails from /keys response and login session result, persist them
in the local database, and display them in the sync settings for both
logged-in and logged-out-but-linked states.

Also unify the separate logged-out-linked and logged-in containers into
a single layout that toggles elements based on login state.

Requires dataserver and stream-server changes
2026-03-24 12:09:08 -04:00

204 lines
6.9 KiB
JavaScript

describe("Sync Preferences", function () {
var win, doc;
before(function* () {
// Load prefs with sync pane
win = yield loadWindow("chrome://zotero/content/preferences/preferences.xhtml", {
pane: 'zotero-prefpane-sync'
});
doc = win.document;
yield win.Zotero_Preferences.waitForFirstPaneLoad();
});
after(function () {
win.close();
});
describe("Settings", function () {
describe("Data Syncing", function () {
var createLoginSessionStub, checkLoginSessionStub, deleteAPIKey, launchURLStub,
streamerSubscribeStub, streamerUnsubscribeStub, indicatorElem, apiKey;
var performLogin = async function (username) {
apiKey = Zotero.Utilities.randomString(24);
createLoginSessionStub.resolves({
sessionToken: 'test-session-token',
loginURL: 'https://www.zotero.org/authorize?token=test-session-token'
});
checkLoginSessionStub.resolves({
status: 'completed',
apiKey,
userID: 1,
username
});
await win.Zotero_Preferences.Sync.linkAccount();
};
before(function* () {
createLoginSessionStub = sinon.stub(
Zotero.Sync.APIClient.prototype, 'createLoginSession');
checkLoginSessionStub = sinon.stub(
Zotero.Sync.APIClient.prototype, 'checkLoginSession');
deleteAPIKey = sinon.stub(Zotero.Sync.APIClient.prototype, 'deleteAPIKey').resolves();
launchURLStub = sinon.stub(Zotero, 'launchURL');
streamerSubscribeStub = sinon.stub(Zotero.Streamer, 'subscribe').returns(false);
streamerUnsubscribeStub = sinon.stub(Zotero.Streamer, 'unsubscribe');
indicatorElem = doc.querySelector('.account-login-status-indicator');
sinon.stub(Zotero, 'alert');
// Speed up polling for tests
win.Zotero_Preferences.Sync._pollInterval = 10;
});
beforeEach(function* () {
yield win.Zotero_Preferences.Sync.unlinkAccount(false);
deleteAPIKey.resetHistory();
createLoginSessionStub.resetHistory();
checkLoginSessionStub.resetHistory();
launchURLStub.resetHistory();
streamerSubscribeStub.resetHistory();
streamerUnsubscribeStub.resetHistory();
Zotero.alert.reset();
});
after(function () {
Zotero.HTTP.mock = null;
Zotero.alert.restore();
createLoginSessionStub.restore();
checkLoginSessionStub.restore();
deleteAPIKey.restore();
launchURLStub.restore();
streamerSubscribeStub.restore();
streamerUnsubscribeStub.restore();
win.Zotero_Preferences.Sync._pollInterval = 3000;
});
it("should set API key and display full controls after successful login", async function () {
await performLogin("Username");
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey);
assert.equal(doc.getElementById('sync-unauthorized').getAttribute('hidden'), 'true');
assert.isTrue(launchURLStub.calledOnce);
});
it("should show error when login session expires", async function () {
createLoginSessionStub.resolves({
sessionToken: 'test-session-token',
loginURL: 'https://www.zotero.org/authorize?token=test-session-token'
});
let expiredError = new Error("Login session expired");
expiredError.expired = true;
checkLoginSessionStub.rejects(expiredError);
await win.Zotero_Preferences.Sync.linkAccount();
assert.isTrue(Zotero.alert.called);
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), "");
assert.equal(doc.querySelector('.sync-settings').hidden, true);
});
it("should reset UI when login session is cancelled on server", async function () {
createLoginSessionStub.resolves({
sessionToken: 'test-session-token',
loginURL: 'https://www.zotero.org/authorize?token=test-session-token'
});
checkLoginSessionStub.resolves({
status: 'cancelled'
});
await win.Zotero_Preferences.Sync.linkAccount();
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), "");
assert.equal(doc.querySelector('.account-login-default').hidden, false);
assert.equal(doc.querySelector('.account-login-pending').hidden, true);
});
it("should delete API key and display auth form when 'Unlink Account' clicked", async function () {
await performLogin("Username");
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey);
await win.Zotero_Preferences.Sync.unlinkAccount(false);
assert.isTrue(deleteAPIKey.called);
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), "");
assert.equal(doc.querySelector('.sync-settings').hidden, true);
});
it("should reset the storage controller when unlinking", async function () {
await performLogin("Username");
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey);
let options = {
apiClient: Zotero.Sync.Runner.getAPIClient({ apiKey })
};
let controller = Zotero.Sync.Runner.getStorageController('zfs', options);
let apiKey1 = controller.apiClient.apiKey;
await win.Zotero_Preferences.Sync.unlinkAccount(false);
await performLogin("Username");
options = {
apiClient: Zotero.Sync.Runner.getAPIClient({ apiKey })
};
controller = Zotero.Sync.Runner.getStorageController('zfs', options);
assert.notEqual(controller.apiClient.apiKey, apiKey1);
});
it("should not unlink on pressing cancel", async function () {
await performLogin("Username");
waitForDialog(null, 'cancel');
await win.Zotero_Preferences.Sync.unlinkAccount();
assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey);
assert.equal(doc.getElementById('sync-unauthorized').getAttribute('hidden'), 'true');
});
it("should clear sync errors from the toolbar after logging in", async function () {
let win = await loadZoteroPane();
let syncError = win.document.getElementById('zotero-tb-sync-error');
Zotero.Sync.Runner.updateIcons(new Error("a sync error"));
assert.isFalse(syncError.hidden);
await performLogin("Username");
assert.isTrue(syncError.hidden);
win.close();
});
it("should cancel login and reset UI when cancelLogin is called", async function () {
let cancelLoginSessionStub = sinon.stub(
Zotero.Sync.APIClient.prototype, 'cancelLoginSession').resolves();
createLoginSessionStub.resolves({
sessionToken: 'test-session-token',
loginURL: 'https://www.zotero.org/authorize?token=test-session-token'
});
// Return "pending" so the poll loop keeps iterating
checkLoginSessionStub.resolves({ status: 'pending' });
// Start login but don't await -- it will keep polling
let loginPromise = win.Zotero_Preferences.Sync.linkAccount();
// Wait for the poll loop to start
await Zotero.Promise.delay(50);
win.Zotero_Preferences.Sync.cancelLogin();
// Wait for the login promise to resolve after cancellation
await loginPromise;
assert.equal(doc.querySelector('.account-login-default').hidden, false);
assert.equal(doc.querySelector('.account-login-pending').hidden, true);
assert.isTrue(cancelLoginSessionStub.calledWith('test-session-token'));
cancelLoginSessionStub.restore();
});
});
});
});