1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-12 08:54:00 +02:00
joplin/CliClient/tests/synchronizer.js

1103 lines
38 KiB
JavaScript

require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js');
const { shim } = require('lib/shim.js');
const fs = require('fs-extra');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const Tag = require('lib/models/Tag.js');
const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting.js');
const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000 + 30000; // The first test is slow because the database needs to be built
async function allItems() {
let folders = await Folder.all();
let notes = await Note.all();
return folders.concat(notes);
}
async function allSyncTargetItemsEncrypted() {
const list = await fileApi().list();
const files = list.items;
//console.info(Setting.value('resourceDir'));
let totalCount = 0;
let encryptedCount = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
const remoteContentString = await fileApi().get(file.path);
const remoteContent = await BaseItem.unserialize(remoteContentString);
const ItemClass = BaseItem.itemClass(remoteContent);
if (!ItemClass.encryptionSupported()) continue;
totalCount++;
if (remoteContent.type_ === BaseModel.TYPE_RESOURCE) {
const content = await fileApi().get('.resource/' + remoteContent.id);
totalCount++;
if (content.substr(0, 5) === 'JED01') output = encryptedCount++;
}
if (!!remoteContent.encryption_applied) encryptedCount++;
}
if (!totalCount) throw new Error('No encryptable item on sync target');
return totalCount === encryptedCount;
}
async function localItemsSameAsRemote(locals, expect) {
let error = null;
try {
let files = await fileApi().list();
files = files.items;
expect(locals.length).toBe(files.length);
for (let i = 0; i < locals.length; i++) {
let dbItem = locals[i];
let path = BaseItem.systemPath(dbItem);
let remote = await fileApi().stat(path);
expect(!!remote).toBe(true);
if (!remote) continue;
// if (syncTargetId() == SyncTargetRegistry.nameToId('filesystem')) {
// expect(remote.updated_time).toBe(Math.floor(dbItem.updated_time / 1000) * 1000);
// } else {
// expect(remote.updated_time).toBe(dbItem.updated_time);
// }
let remoteContent = await fileApi().get(path);
remoteContent = dbItem.type_ == BaseModel.TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
expect(remoteContent.title).toBe(dbItem.title);
}
} catch (e) {
error = e;
}
expect(error).toBe(null);
}
let insideBeforeEach = false;
describe('Synchronizer', function() {
beforeEach(async (done) => {
insideBeforeEach = true;
await setupDatabaseAndSynchronizer(1);
await setupDatabaseAndSynchronizer(2);
await switchClient(1);
done();
insideBeforeEach = false;
});
// it('should create remote items', asyncTest(async () => {
// let folder = await Folder.save({ title: "folder1" });
// await Note.save({ title: "un", parent_id: folder.id });
// let all = await allItems();
// await synchronizer().start();
// await localItemsSameAsRemote(all, expect);
// }));
// it('should update remote items', asyncTest(async () => {
// let folder = await Folder.save({ title: "folder1" });
// let note = await Note.save({ title: "un", parent_id: folder.id });
// await synchronizer().start();
// await Note.save({ title: "un UPDATE", id: note.id });
// let all = await allItems();
// await synchronizer().start();
// await localItemsSameAsRemote(all, expect);
// }));
// it('should create local items', asyncTest(async () => {
// let folder = await Folder.save({ title: "folder1" });
// await Note.save({ title: "un", parent_id: folder.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// let all = await allItems();
// await localItemsSameAsRemote(all, expect);
// }));
// it('should update local items', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await sleep(0.1);
// let note2 = await Note.load(note1.id);
// note2.title = "Updated on client 2";
// await Note.save(note2);
// note2 = await Note.load(note2.id);
// await synchronizer().start();
// await switchClient(1);
// await synchronizer().start();
// let all = await allItems();
// await localItemsSameAsRemote(all, expect);
// }));
// it('should resolve note conflicts', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// let note2 = await Note.load(note1.id);
// note2.title = "Updated on client 2";
// await Note.save(note2);
// note2 = await Note.load(note2.id);
// await synchronizer().start();
// await switchClient(1);
// let note2conf = await Note.load(note1.id);
// note2conf.title = "Updated on client 1";
// await Note.save(note2conf);
// note2conf = await Note.load(note1.id);
// await synchronizer().start();
// let conflictedNotes = await Note.conflictedNotes();
// expect(conflictedNotes.length).toBe(1);
// // Other than the id (since the conflicted note is a duplicate), and the is_conflict property
// // the conflicted and original note must be the same in every way, to make sure no data has been lost.
// let conflictedNote = conflictedNotes[0];
// expect(conflictedNote.id == note2conf.id).toBe(false);
// for (let n in conflictedNote) {
// if (!conflictedNote.hasOwnProperty(n)) continue;
// if (n == 'id' || n == 'is_conflict') continue;
// expect(conflictedNote[n]).toBe(note2conf[n], 'Property: ' + n);
// }
// let noteUpdatedFromRemote = await Note.load(note1.id);
// for (let n in noteUpdatedFromRemote) {
// if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue;
// expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n);
// }
// }));
// it('should resolve folders conflicts', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2); // ----------------------------------
// await synchronizer().start();
// await sleep(0.1);
// let folder1_modRemote = await Folder.load(folder1.id);
// folder1_modRemote.title = "folder1 UPDATE CLIENT 2";
// await Folder.save(folder1_modRemote);
// folder1_modRemote = await Folder.load(folder1_modRemote.id);
// await synchronizer().start();
// await switchClient(1); // ----------------------------------
// await sleep(0.1);
// let folder1_modLocal = await Folder.load(folder1.id);
// folder1_modLocal.title = "folder1 UPDATE CLIENT 1";
// await Folder.save(folder1_modLocal);
// folder1_modLocal = await Folder.load(folder1.id);
// await synchronizer().start();
// let folder1_final = await Folder.load(folder1.id);
// expect(folder1_final.title).toBe(folder1_modRemote.title);
// }));
// it('should delete remote notes', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await sleep(0.1);
// await Note.delete(note1.id);
// await synchronizer().start();
// let files = await fileApi().list();
// files = files.items;
// expect(files.length).toBe(1);
// expect(files[0].path).toBe(Folder.systemPath(folder1));
// let deletedItems = await BaseItem.deletedItems(syncTargetId());
// expect(deletedItems.length).toBe(0);
// }));
// it('should not created deleted_items entries for items deleted via sync', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await Folder.delete(folder1.id);
// await synchronizer().start();
// await switchClient(1);
// await synchronizer().start();
// let deletedItems = await BaseItem.deletedItems(syncTargetId());
// expect(deletedItems.length).toBe(0);
// }));
// it('should delete local notes', asyncTest(async () => {
// // For these tests we pass the context around for each user. This is to make sure that the "deletedItemsProcessed"
// // property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared
// // it means items will no longer be deleted locally via sync.
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// let note2 = await Note.save({ title: "deux", parent_id: folder1.id });
// let context1 = await synchronizer().start();
// await switchClient(2);
// let context2 = await synchronizer().start();
// await Note.delete(note1.id);
// context2 = await synchronizer().start({ context: context2 });
// await switchClient(1);
// context1 = await synchronizer().start({ context: context1 });
// let items = await allItems();
// expect(items.length).toBe(2);
// let deletedItems = await BaseItem.deletedItems(syncTargetId());
// expect(deletedItems.length).toBe(0);
// await Note.delete(note2.id);
// context1 = await synchronizer().start({ context: context1 });
// }));
// it('should delete remote folder', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let folder2 = await Folder.save({ title: "folder2" });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await sleep(0.1);
// await Folder.delete(folder2.id);
// await synchronizer().start();
// let all = await allItems();
// await localItemsSameAsRemote(all, expect);
// }));
// it('should delete local folder', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let folder2 = await Folder.save({ title: "folder2" });
// let context1 = await synchronizer().start();
// await switchClient(2);
// let context2 = await synchronizer().start();
// await Folder.delete(folder2.id);
// await synchronizer().start({ context: context2 });
// await switchClient(1);
// await synchronizer().start({ context: context1 });
// let items = await allItems();
// await localItemsSameAsRemote(items, expect);
// }));
// it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await Folder.delete(folder1.id);
// await synchronizer().start();
// await switchClient(1);
// let note = await Note.save({ title: "note1", parent_id: folder1.id });
// await synchronizer().start();
// let items = await allItems();
// expect(items.length).toBe(1);
// expect(items[0].title).toBe('note1');
// expect(items[0].is_conflict).toBe(1);
// }));
// it('should resolve conflict if note has been deleted remotely and locally', asyncTest(async () => {
// let folder = await Folder.save({ title: "folder" });
// let note = await Note.save({ title: "note", parent_id: folder.title });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await Note.delete(note.id);
// await synchronizer().start();
// await switchClient(1);
// await Note.delete(note.id);
// await synchronizer().start();
// let items = await allItems();
// expect(items.length).toBe(1);
// expect(items[0].title).toBe('folder');
// await localItemsSameAsRemote(items, expect);
// }));
// it('should cross delete all folders', asyncTest(async () => {
// // If client1 and 2 have two folders, client 1 deletes item 1 and client
// // 2 deletes item 2, they should both end up with no items after sync.
// let folder1 = await Folder.save({ title: "folder1" });
// let folder2 = await Folder.save({ title: "folder2" });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await sleep(0.1);
// await Folder.delete(folder1.id);
// await switchClient(1);
// await Folder.delete(folder2.id);
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// let items2 = await allItems();
// await switchClient(1);
// await synchronizer().start();
// let items1 = await allItems();
// expect(items1.length).toBe(0);
// expect(items1.length).toBe(items2.length);
// }));
// it('should handle conflict when remote note is deleted then local note is modified', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await sleep(0.1);
// await Note.delete(note1.id);
// await synchronizer().start();
// await switchClient(1);
// let newTitle = 'Modified after having been deleted';
// await Note.save({ id: note1.id, title: newTitle });
// await synchronizer().start();
// let conflictedNotes = await Note.conflictedNotes();
// expect(conflictedNotes.length).toBe(1);
// expect(conflictedNotes[0].title).toBe(newTitle);
// let unconflictedNotes = await Note.unconflictedNotes();
// expect(unconflictedNotes.length).toBe(0);
// }));
// it('should handle conflict when remote folder is deleted then local folder is renamed', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let folder2 = await Folder.save({ title: "folder2" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await sleep(0.1);
// await Folder.delete(folder1.id);
// await synchronizer().start();
// await switchClient(1);
// await sleep(0.1);
// let newTitle = 'Modified after having been deleted';
// await Folder.save({ id: folder1.id, title: newTitle });
// await synchronizer().start();
// let items = await allItems();
// expect(items.length).toBe(1);
// }));
// it('should allow duplicate folder titles', asyncTest(async () => {
// let localF1 = await Folder.save({ title: "folder" });
// await switchClient(2);
// let remoteF2 = await Folder.save({ title: "folder" });
// await synchronizer().start();
// await switchClient(1);
// await sleep(0.1);
// await synchronizer().start();
// let localF2 = await Folder.load(remoteF2.id);
// expect(localF2.title == remoteF2.title).toBe(true);
// // Then that folder that has been renamed locally should be set in such a way
// // that synchronizing it applies the title change remotely, and that new title
// // should be retrieved by client 2.
// await synchronizer().start();
// await switchClient(2);
// await sleep(0.1);
// await synchronizer().start();
// remoteF2 = await Folder.load(remoteF2.id);
// expect(remoteF2.title == localF2.title).toBe(true);
// }));
// async function shoudSyncTagTest(withEncryption) {
// let masterKey = null;
// if (withEncryption) {
// Setting.setValue('encryption.enabled', true);
// masterKey = await loadEncryptionMasterKey();
// }
// let f1 = await Folder.save({ title: "folder" });
// let n1 = await Note.save({ title: "mynote" });
// let n2 = await Note.save({ title: "mynote2" });
// let tag = await Tag.save({ title: 'mytag' });
// let context1 = await synchronizer().start();
// await switchClient(2);
// let context2 = await synchronizer().start();
// if (withEncryption) {
// const masterKey_2 = await MasterKey.load(masterKey.id);
// await encryptionService().loadMasterKey(masterKey_2, '123456', true);
// let t = await Tag.load(tag.id);
// await Tag.decrypt(t);
// }
// let remoteTag = await Tag.loadByTitle(tag.title);
// expect(!!remoteTag).toBe(true);
// expect(remoteTag.id).toBe(tag.id);
// await Tag.addNote(remoteTag.id, n1.id);
// await Tag.addNote(remoteTag.id, n2.id);
// let noteIds = await Tag.noteIds(tag.id);
// expect(noteIds.length).toBe(2);
// context2 = await synchronizer().start({ context: context2 });
// await switchClient(1);
// context1 = await synchronizer().start({ context: context1 });
// let remoteNoteIds = await Tag.noteIds(tag.id);
// expect(remoteNoteIds.length).toBe(2);
// await Tag.removeNote(tag.id, n1.id);
// remoteNoteIds = await Tag.noteIds(tag.id);
// expect(remoteNoteIds.length).toBe(1);
// context1 = await synchronizer().start({ context: context1 });
// await switchClient(2);
// context2 = await synchronizer().start({ context: context2 });
// noteIds = await Tag.noteIds(tag.id);
// expect(noteIds.length).toBe(1);
// expect(remoteNoteIds[0]).toBe(noteIds[0]);
// }
// it('should sync tags', asyncTest(async () => {
// await shoudSyncTagTest(false);
// }));
// it('should sync encrypted tags', asyncTest(async () => {
// await shoudSyncTagTest(true);
// }));
// it('should not sync notes with conflicts', asyncTest(async () => {
// let f1 = await Folder.save({ title: "folder" });
// let n1 = await Note.save({ title: "mynote", parent_id: f1.id, is_conflict: 1 });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// let notes = await Note.all();
// let folders = await Folder.all()
// expect(notes.length).toBe(0);
// expect(folders.length).toBe(1);
// }));
// it('should not try to delete on remote conflicted notes that have been deleted', asyncTest(async () => {
// let f1 = await Folder.save({ title: "folder" });
// let n1 = await Note.save({ title: "mynote", parent_id: f1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await Note.save({ id: n1.id, is_conflict: 1 });
// await Note.delete(n1.id);
// const deletedItems = await BaseItem.deletedItems(syncTargetId());
// expect(deletedItems.length).toBe(0);
// }));
// async function ignorableNoteConflictTest(withEncryption) {
// if (withEncryption) {
// Setting.setValue('encryption.enabled', true);
// await loadEncryptionMasterKey();
// }
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// if (withEncryption) {
// await loadEncryptionMasterKey(null, true);
// await decryptionWorker().start();
// }
// let note2 = await Note.load(note1.id);
// note2.todo_completed = time.unixMs()-1;
// await Note.save(note2);
// note2 = await Note.load(note2.id);
// await synchronizer().start();
// await switchClient(1);
// let note2conf = await Note.load(note1.id);
// note2conf.todo_completed = time.unixMs();
// await Note.save(note2conf);
// note2conf = await Note.load(note1.id);
// await synchronizer().start();
// if (!withEncryption) {
// // That was previously a common conflict:
// // - Client 1 mark todo as "done", and sync
// // - Client 2 doesn't sync, mark todo as "done" todo. Then sync.
// // In theory it is a conflict because the todo_completed dates are different
// // but in practice it doesn't matter, we can just take the date when the
// // todo was marked as "done" the first time.
// let conflictedNotes = await Note.conflictedNotes();
// expect(conflictedNotes.length).toBe(0);
// let notes = await Note.all();
// expect(notes.length).toBe(1);
// expect(notes[0].id).toBe(note1.id);
// expect(notes[0].todo_completed).toBe(note2.todo_completed);
// } else {
// // If the notes are encrypted however it's not possible to do this kind of
// // smart conflict resolving since we don't know the content, so in that
// // case it's handled as a regular conflict.
// let conflictedNotes = await Note.conflictedNotes();
// expect(conflictedNotes.length).toBe(1);
// let notes = await Note.all();
// expect(notes.length).toBe(2);
// }
// }
// it('should not consider it is a conflict if neither the title nor body of the note have changed', asyncTest(async () => {
// await ignorableNoteConflictTest(false);
// }));
// it('should always handle conflict if local or remote are encrypted', asyncTest(async () => {
// await ignorableNoteConflictTest(true);
// }));
// it('items should be downloaded again when user cancels in the middle of delta operation', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
// await synchronizer().start();
// await switchClient(2);
// synchronizer().testingHooks_ = ['cancelDeltaLoop2'];
// let context = await synchronizer().start();
// let notes = await Note.all();
// expect(notes.length).toBe(0);
// synchronizer().testingHooks_ = [];
// await synchronizer().start({ context: context });
// notes = await Note.all();
// expect(notes.length).toBe(1);
// }));
// it('should skip items that cannot be synced', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
// const noteId = note1.id;
// await synchronizer().start();
// let disabledItems = await BaseItem.syncDisabledItems(syncTargetId());
// expect(disabledItems.length).toBe(0);
// await Note.save({ id: noteId, title: "un mod", });
// synchronizer().testingHooks_ = ['rejectedByTarget'];
// await synchronizer().start();
// synchronizer().testingHooks_ = [];
// await synchronizer().start(); // Another sync to check that this item is now excluded from sync
// await switchClient(2);
// await synchronizer().start();
// let notes = await Note.all();
// expect(notes.length).toBe(1);
// expect(notes[0].title).toBe('un');
// await switchClient(1);
// disabledItems = await BaseItem.syncDisabledItems(syncTargetId());
// expect(disabledItems.length).toBe(1);
// }));
// it('notes and folders should get encrypted when encryption is enabled', asyncTest(async () => {
// Setting.setValue('encryption.enabled', true);
// const masterKey = await loadEncryptionMasterKey();
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", body: 'to be encrypted', parent_id: folder1.id });
// await synchronizer().start();
// // After synchronisation, remote items should be encrypted but local ones remain plain text
// note1 = await Note.load(note1.id);
// expect(note1.title).toBe('un');
// await switchClient(2);
// await synchronizer().start();
// let folder1_2 = await Folder.load(folder1.id);
// let note1_2 = await Note.load(note1.id);
// let masterKey_2 = await MasterKey.load(masterKey.id);
// // On this side however it should be received encrypted
// expect(!note1_2.title).toBe(true);
// expect(!folder1_2.title).toBe(true);
// expect(!!note1_2.encryption_cipher_text).toBe(true);
// expect(!!folder1_2.encryption_cipher_text).toBe(true);
// // Master key is already encrypted so it does not get re-encrypted during sync
// expect(masterKey_2.content).toBe(masterKey.content);
// expect(masterKey_2.checksum).toBe(masterKey.checksum);
// // Now load the master key we got from client 1 and try to decrypt
// await encryptionService().loadMasterKey(masterKey_2, '123456', true);
// // Get the decrypted items back
// await Folder.decrypt(folder1_2);
// await Note.decrypt(note1_2);
// folder1_2 = await Folder.load(folder1.id);
// note1_2 = await Note.load(note1.id);
// // Check that properties match the original items. Also check
// // the encryption did not affect the updated_time timestamp.
// expect(note1_2.title).toBe(note1.title);
// expect(note1_2.body).toBe(note1.body);
// expect(note1_2.updated_time).toBe(note1.updated_time);
// expect(!note1_2.encryption_cipher_text).toBe(true);
// expect(folder1_2.title).toBe(folder1.title);
// expect(folder1_2.updated_time).toBe(folder1.updated_time);
// expect(!folder1_2.encryption_cipher_text).toBe(true);
// }));
// it('should enable encryption automatically when downloading new master key (and none was previously available)',asyncTest(async () => {
// // Enable encryption on client 1 and sync an item
// Setting.setValue('encryption.enabled', true);
// await loadEncryptionMasterKey();
// let folder1 = await Folder.save({ title: "folder1" });
// await synchronizer().start();
// await switchClient(2);
// // Synchronising should enable encryption since we're going to get a master key
// expect(Setting.value('encryption.enabled')).toBe(false);
// await synchronizer().start();
// expect(Setting.value('encryption.enabled')).toBe(true);
// // Check that we got the master key from client 1
// const masterKey = (await MasterKey.all())[0];
// expect(!!masterKey).toBe(true);
// // Since client 2 hasn't supplied a password yet, no master key is currently loaded
// expect(encryptionService().loadedMasterKeyIds().length).toBe(0);
// // If we sync now, nothing should be sent to target since we don't have a password.
// // Technically it's incorrect to set the property of an encrypted variable but it allows confirming
// // that encryption doesn't work if user hasn't supplied a password.
// await BaseItem.forceSync(folder1.id);
// await synchronizer().start();
// await switchClient(1);
// await synchronizer().start();
// folder1 = await Folder.load(folder1.id);
// expect(folder1.title).toBe('folder1'); // Still at old value
// await switchClient(2);
// // Now client 2 set the master key password
// Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
// await encryptionService().loadMasterKeysFromSettings();
// // Now that master key should be loaded
// expect(encryptionService().loadedMasterKeyIds()[0]).toBe(masterKey.id);
// // Decrypt all the data. Now change the title and sync again - this time the changes should be transmitted
// await decryptionWorker().start();
// folder1_2 = await Folder.save({ id: folder1.id, title: "change test" });
// // If we sync now, this time client 1 should get the changes we did earlier
// await synchronizer().start();
// await switchClient(1);
// await synchronizer().start();
// // Decrypt the data we just got
// await decryptionWorker().start();
// folder1 = await Folder.load(folder1.id);
// expect(folder1.title).toBe('change test'); // Got title from client 2
// }));
// it('should encrypt existing notes too when enabling E2EE', asyncTest(async () => {
// // First create a folder, without encryption enabled, and sync it
// let folder1 = await Folder.save({ title: "folder1" });
// await synchronizer().start();
// let files = await fileApi().list()
// let content = await fileApi().get(files.items[0].path);
// expect(content.indexOf('folder1') >= 0).toBe(true)
// // Then enable encryption and sync again
// let masterKey = await encryptionService().generateMasterKey('123456');
// masterKey = await MasterKey.save(masterKey);
// await encryptionService().enableEncryption(masterKey, '123456');
// await encryptionService().loadMasterKeysFromSettings();
// await synchronizer().start();
// // Even though the folder has not been changed it should have been synced again so that
// // an encrypted version of it replaces the decrypted version.
// files = await fileApi().list()
// expect(files.items.length).toBe(2);
// // By checking that the folder title is not present, we can confirm that the item has indeed been encrypted
// // One of the two items is the master key
// content = await fileApi().get(files.items[0].path);
// expect(content.indexOf('folder1') < 0).toBe(true);
// content = await fileApi().get(files.items[1].path);
// expect(content.indexOf('folder1') < 0).toBe(true);
// }));
it('should sync resources', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
let resourcePath1 = Resource.fullPath(resource1);
await synchronizer().start();
expect((await fileApi().list()).items.length).toBe(3);
await switchClient(2);
await synchronizer().start();
let allResources = await Resource.all();
expect(allResources.length).toBe(1);
let resource1_2 = allResources[0];
let ls = await Resource.localState(resource1_2);
expect(resource1_2.id).toBe(resource1.id);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_IDLE);
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
fetcher.queueDownload(resource1_2.id);
await fetcher.waitForAllFinished();
resource1_2 = await Resource.load(resource1.id);
ls = await Resource.localState(resource1_2);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
let resourcePath1_2 = Resource.fullPath(resource1_2);
expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true);
}));
it('should handle resource download errors', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
let resourcePath1 = Resource.fullPath(resource1);
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
const fetcher = new ResourceFetcher(() => { return {
// Simulate a failed download
get: () => { return new Promise((resolve, reject) => { reject(new Error('did not work')) }); }
} });
fetcher.queueDownload(resource1.id);
await fetcher.waitForAllFinished();
resource1 = await Resource.load(resource1.id);
let ls = await Resource.localState(resource1);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_ERROR);
expect(ls.fetch_error).toBe('did not work');
}));
// it('should delete resources', asyncTest(async () => {
// while (insideBeforeEach) await time.msleep(500);
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
// await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
// let resource1 = (await Resource.all())[0];
// let resourcePath1 = Resource.fullPath(resource1);
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// let allResources = await Resource.all();
// expect(allResources.length).toBe(1);
// let all = await fileApi().list();
// expect(all.items.length).toBe(3);
// await Resource.delete(resource1.id);
// await synchronizer().start();
// all = await fileApi().list();
// expect(all.items.length).toBe(2);
// await switchClient(1);
// expect(await shim.fsDriver().exists(resourcePath1)).toBe(true);
// await synchronizer().start();
// allResources = await Resource.all();
// expect(allResources.length).toBe(0);
// expect(await shim.fsDriver().exists(resourcePath1)).toBe(false);
// }));
// it('should encryt resources', asyncTest(async () => {
// Setting.setValue('encryption.enabled', true);
// const masterKey = await loadEncryptionMasterKey();
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
// await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
// let resource1 = (await Resource.all())[0];
// let resourcePath1 = Resource.fullPath(resource1);
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
// await encryptionService().loadMasterKeysFromSettings();
// const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
// fetcher.queueDownload(resource1.id);
// await fetcher.waitForAllFinished();
// let resource1_2 = (await Resource.all())[0];
// resource1_2 = await Resource.decrypt(resource1_2);
// let resourcePath1_2 = Resource.fullPath(resource1_2);
// expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true);
// }));
// it('should upload decrypted items to sync target after encryption disabled', asyncTest(async () => {
// Setting.setValue('encryption.enabled', true);
// const masterKey = await loadEncryptionMasterKey();
// let folder1 = await Folder.save({ title: "folder1" });
// await synchronizer().start();
// let allEncrypted = await allSyncTargetItemsEncrypted();
// expect(allEncrypted).toBe(true);
// await encryptionService().disableEncryption();
// await synchronizer().start();
// allEncrypted = await allSyncTargetItemsEncrypted();
// expect(allEncrypted).toBe(false);
// }));
// it('should not upload any item if encryption was enabled, and items have not been decrypted, and then encryption disabled', asyncTest(async () => {
// // For some reason I can't explain, this test is sometimes executed before beforeEach is finished
// // which means it's going to fail in unexpected way. So the loop below wait for beforeEach to be done.
// while (insideBeforeEach) await time.msleep(100);
// Setting.setValue('encryption.enabled', true);
// const masterKey = await loadEncryptionMasterKey();
// let folder1 = await Folder.save({ title: "folder1" });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// expect(Setting.value('encryption.enabled')).toBe(true);
// // If we try to disable encryption now, it should throw an error because some items are
// // currently encrypted. They must be decrypted first so that they can be sent as
// // plain text to the sync target.
// //let hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption());
// //expect(hasThrown).toBe(true);
// // Now supply the password, and decrypt the items
// Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
// await encryptionService().loadMasterKeysFromSettings();
// await decryptionWorker().start();
// // Try to disable encryption again
// hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption());
// expect(hasThrown).toBe(false);
// // If we sync now the target should receive the decrypted items
// await synchronizer().start();
// allEncrypted = await allSyncTargetItemsEncrypted();
// expect(allEncrypted).toBe(false);
// }));
// it('should encrypt remote resources after encryption has been enabled', asyncTest(async () => {
// while (insideBeforeEach) await time.msleep(100);
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
// await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
// let resource1 = (await Resource.all())[0];
// await synchronizer().start();
// expect(await allSyncTargetItemsEncrypted()).toBe(false);
// const masterKey = await loadEncryptionMasterKey();
// await encryptionService().enableEncryption(masterKey, '123456');
// await encryptionService().loadMasterKeysFromSettings();
// await synchronizer().start();
// expect(await allSyncTargetItemsEncrypted()).toBe(true);
// }));
// it('should upload encrypted resource, but it should not mark the blob as encrypted locally', asyncTest(async () => {
// while (insideBeforeEach) await time.msleep(100);
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
// await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
// const masterKey = await loadEncryptionMasterKey();
// await encryptionService().enableEncryption(masterKey, '123456');
// await encryptionService().loadMasterKeysFromSettings();
// await synchronizer().start();
// let resource1 = (await Resource.all())[0];
// expect(resource1.encryption_blob_encrypted).toBe(0);
// }));
// it('should create remote items with UTF-8 content', asyncTest(async () => {
// let folder = await Folder.save({ title: "Fahrräder" });
// await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id });
// let all = await allItems();
// await synchronizer().start();
// await localItemsSameAsRemote(all, expect);
// }));
// it("should update remote items but not pull remote changes", asyncTest(async () => {
// let folder = await Folder.save({ title: "folder1" });
// let note = await Note.save({ title: "un", parent_id: folder.id });
// await synchronizer().start();
// await switchClient(2);
// await synchronizer().start();
// await Note.save({ title: "deux", parent_id: folder.id });
// await synchronizer().start();
// await switchClient(1);
// await Note.save({ title: "un UPDATE", id: note.id });
// await synchronizer().start({ syncSteps: ["update_remote"] });
// let all = await allItems();
// expect(all.length).toBe(2);
// await switchClient(2);
// await synchronizer().start();
// let note2 = await Note.load(note.id);
// expect(note2.title).toBe("un UPDATE");
// }));
});