2018-03-09 22:59:12 +02:00
|
|
|
const fs = require('fs-extra');
|
|
|
|
const { JoplinDatabase } = require('lib/joplin-database.js');
|
|
|
|
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
|
|
|
const BaseModel = require('lib/BaseModel.js');
|
|
|
|
const Folder = require('lib/models/Folder.js');
|
|
|
|
const Note = require('lib/models/Note.js');
|
|
|
|
const Resource = require('lib/models/Resource.js');
|
|
|
|
const Tag = require('lib/models/Tag.js');
|
|
|
|
const NoteTag = require('lib/models/NoteTag.js');
|
|
|
|
const { Logger } = require('lib/logger.js');
|
|
|
|
const Setting = require('lib/models/Setting.js');
|
|
|
|
const MasterKey = require('lib/models/MasterKey');
|
|
|
|
const BaseItem = require('lib/models/BaseItem.js');
|
|
|
|
const { Synchronizer } = require('lib/synchronizer.js');
|
|
|
|
const { FileApi } = require('lib/file-api.js');
|
|
|
|
const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
|
|
|
|
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
|
|
|
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav.js');
|
2018-03-24 21:35:10 +02:00
|
|
|
const { FileApiDriverDropbox } = require('lib/file-api-driver-dropbox.js');
|
2018-03-15 19:46:54 +02:00
|
|
|
const BaseService = require('lib/services/BaseService.js');
|
2018-03-09 22:59:12 +02:00
|
|
|
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
|
|
|
const { time } = require('lib/time-utils.js');
|
|
|
|
const { shimInit } = require('lib/shim-init-node.js');
|
|
|
|
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
|
|
|
const SyncTargetMemory = require('lib/SyncTargetMemory.js');
|
|
|
|
const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js');
|
|
|
|
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
|
|
|
|
const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js');
|
2018-03-24 21:35:10 +02:00
|
|
|
const SyncTargetDropbox = require('lib/SyncTargetDropbox.js');
|
2018-03-09 22:59:12 +02:00
|
|
|
const EncryptionService = require('lib/services/EncryptionService.js');
|
|
|
|
const DecryptionWorker = require('lib/services/DecryptionWorker.js');
|
|
|
|
const WebDavApi = require('lib/WebDavApi');
|
2018-03-24 21:35:10 +02:00
|
|
|
const DropboxApi = require('lib/DropboxApi');
|
2017-06-14 21:59:02 +02:00
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
let databases_ = [];
|
|
|
|
let synchronizers_ = [];
|
2017-12-13 20:57:40 +02:00
|
|
|
let encryptionServices_ = [];
|
2017-12-19 21:01:29 +02:00
|
|
|
let decryptionWorkers_ = [];
|
2017-06-14 21:59:02 +02:00
|
|
|
let fileApi_ = null;
|
2017-06-18 22:19:13 +02:00
|
|
|
let currentClient_ = 1;
|
2017-06-14 21:59:02 +02:00
|
|
|
|
2017-12-13 20:57:40 +02:00
|
|
|
shimInit();
|
|
|
|
|
2017-07-06 00:29:03 +02:00
|
|
|
const fsDriver = new FsDriverNode();
|
|
|
|
Logger.fsDriver_ = fsDriver;
|
|
|
|
Resource.fsDriver_ = fsDriver;
|
2017-12-18 21:54:03 +02:00
|
|
|
EncryptionService.fsDriver_ = fsDriver;
|
2018-01-17 20:51:15 +02:00
|
|
|
FileApiDriverLocal.fsDriver_ = fsDriver;
|
2017-07-06 00:29:03 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
const logDir = __dirname + '/../tests/logs';
|
2017-07-06 21:48:17 +02:00
|
|
|
fs.mkdirpSync(logDir, 0o755);
|
|
|
|
|
2017-11-24 21:21:30 +02:00
|
|
|
SyncTargetRegistry.addClass(SyncTargetMemory);
|
|
|
|
SyncTargetRegistry.addClass(SyncTargetFilesystem);
|
2017-11-28 00:50:46 +02:00
|
|
|
SyncTargetRegistry.addClass(SyncTargetOneDrive);
|
2018-01-25 23:15:58 +02:00
|
|
|
SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
2018-03-24 21:35:10 +02:00
|
|
|
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
2017-11-24 21:21:30 +02:00
|
|
|
|
2018-03-09 21:07:38 +02:00
|
|
|
// const syncTargetId_ = SyncTargetRegistry.nameToId("nextcloud");
|
2018-03-24 21:35:10 +02:00
|
|
|
// const syncTargetId_ = SyncTargetRegistry.nameToId("memory");
|
2018-01-25 23:15:58 +02:00
|
|
|
//const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
|
2018-03-24 21:35:10 +02:00
|
|
|
const syncTargetId_ = SyncTargetRegistry.nameToId('dropbox');
|
2018-03-09 22:59:12 +02:00
|
|
|
const syncDir = __dirname + '/../tests/sync';
|
2017-07-23 16:11:44 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;//400;
|
2017-07-24 20:58:11 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTargetId_));
|
2018-01-25 23:15:58 +02:00
|
|
|
|
2017-06-25 17:17:40 +02:00
|
|
|
const logger = new Logger();
|
2018-03-09 22:59:12 +02:00
|
|
|
logger.addTarget('console');
|
|
|
|
logger.addTarget('file', { path: logDir + '/log.txt' });
|
2018-03-15 19:46:54 +02:00
|
|
|
logger.setLevel(Logger.LEVEL_WARN); // Set to DEBUG to display sync process in console
|
2017-06-25 17:17:40 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
BaseItem.loadClass('Note', Note);
|
|
|
|
BaseItem.loadClass('Folder', Folder);
|
|
|
|
BaseItem.loadClass('Resource', Resource);
|
|
|
|
BaseItem.loadClass('Tag', Tag);
|
|
|
|
BaseItem.loadClass('NoteTag', NoteTag);
|
|
|
|
BaseItem.loadClass('MasterKey', MasterKey);
|
2017-07-06 21:48:17 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
|
|
|
Setting.setConstant('appType', 'cli');
|
2017-07-07 00:15:31 +02:00
|
|
|
|
2018-03-15 19:46:54 +02:00
|
|
|
BaseService.logger_ = logger;
|
|
|
|
|
2018-02-15 19:12:09 +02:00
|
|
|
Setting.autoSaveEnabled = false;
|
|
|
|
|
2017-07-23 16:11:44 +02:00
|
|
|
function syncTargetId() {
|
2017-07-24 20:58:11 +02:00
|
|
|
return syncTargetId_;
|
2017-07-23 16:11:44 +02:00
|
|
|
}
|
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
function sleep(n) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
setTimeout(() => {
|
|
|
|
resolve();
|
2017-06-19 21:18:22 +02:00
|
|
|
}, Math.round(n * 1000));
|
2017-06-18 22:19:13 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-03 20:29:19 +02:00
|
|
|
async function switchClient(id) {
|
2017-07-24 20:58:11 +02:00
|
|
|
await time.msleep(sleepTime); // Always leave a little time so that updated_time properties don't overlap
|
2017-07-03 20:29:19 +02:00
|
|
|
await Setting.saveAll();
|
2017-06-20 21:18:19 +02:00
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
currentClient_ = id;
|
|
|
|
BaseModel.db_ = databases_[id];
|
|
|
|
Folder.db_ = databases_[id];
|
|
|
|
Note.db_ = databases_[id];
|
|
|
|
BaseItem.db_ = databases_[id];
|
2017-06-20 21:18:19 +02:00
|
|
|
Setting.db_ = databases_[id];
|
|
|
|
|
2017-12-13 20:57:40 +02:00
|
|
|
BaseItem.encryptionService_ = encryptionServices_[id];
|
2017-12-18 21:54:03 +02:00
|
|
|
Resource.encryptionService_ = encryptionServices_[id];
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
Setting.setConstant('resourceDir', resourceDir(id));
|
2017-12-13 20:57:40 +02:00
|
|
|
|
2017-06-20 21:18:19 +02:00
|
|
|
return Setting.load();
|
2017-06-18 22:19:13 +02:00
|
|
|
}
|
|
|
|
|
2017-12-20 21:45:25 +02:00
|
|
|
async function clearDatabase(id = null) {
|
2017-06-18 22:19:13 +02:00
|
|
|
if (id === null) id = currentClient_;
|
|
|
|
|
|
|
|
let queries = [
|
2018-03-09 22:59:12 +02:00
|
|
|
'DELETE FROM notes',
|
|
|
|
'DELETE FROM folders',
|
|
|
|
'DELETE FROM resources',
|
|
|
|
'DELETE FROM tags',
|
|
|
|
'DELETE FROM note_tags',
|
|
|
|
'DELETE FROM master_keys',
|
2018-03-15 19:46:54 +02:00
|
|
|
'DELETE FROM item_changes',
|
|
|
|
'DELETE FROM note_resources',
|
|
|
|
'DELETE FROM settings',
|
2018-03-09 22:59:12 +02:00
|
|
|
'DELETE FROM deleted_items',
|
|
|
|
'DELETE FROM sync_items',
|
2017-06-18 22:19:13 +02:00
|
|
|
];
|
|
|
|
|
2017-12-20 21:45:25 +02:00
|
|
|
await databases_[id].transactionExecBatch(queries);
|
2017-06-18 22:19:13 +02:00
|
|
|
}
|
|
|
|
|
2017-12-20 21:45:25 +02:00
|
|
|
async function setupDatabase(id = null) {
|
2017-06-18 22:19:13 +02:00
|
|
|
if (id === null) id = currentClient_;
|
|
|
|
|
2017-12-20 21:45:25 +02:00
|
|
|
Setting.cancelScheduleSave();
|
|
|
|
Setting.cache_ = null;
|
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
if (databases_[id]) {
|
2017-12-20 21:45:25 +02:00
|
|
|
await clearDatabase(id);
|
|
|
|
await Setting.load();
|
|
|
|
return;
|
2017-06-14 21:59:02 +02:00
|
|
|
}
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
const filePath = __dirname + '/data/test-' + id + '.sqlite';
|
2017-12-20 21:45:25 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
await fs.unlink(filePath);
|
|
|
|
} catch (error) {
|
2017-06-14 21:59:02 +02:00
|
|
|
// Don't care if the file doesn't exist
|
2018-03-09 22:59:12 +02:00
|
|
|
};
|
2017-12-20 21:45:25 +02:00
|
|
|
|
|
|
|
databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
|
|
|
|
await databases_[id].open({ name: filePath });
|
|
|
|
|
|
|
|
BaseModel.db_ = databases_[id];
|
|
|
|
await Setting.load();
|
2017-06-14 21:59:02 +02:00
|
|
|
}
|
|
|
|
|
2017-12-18 21:54:03 +02:00
|
|
|
function resourceDir(id = null) {
|
|
|
|
if (id === null) id = currentClient_;
|
2018-03-09 22:59:12 +02:00
|
|
|
return __dirname + '/data/resources-' + id;
|
2017-12-18 21:54:03 +02:00
|
|
|
}
|
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
async function setupDatabaseAndSynchronizer(id = null) {
|
|
|
|
if (id === null) id = currentClient_;
|
2017-06-18 01:49:52 +02:00
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
await setupDatabase(id);
|
|
|
|
|
2017-12-20 21:45:25 +02:00
|
|
|
EncryptionService.instance_ = null;
|
|
|
|
DecryptionWorker.instance_ = null;
|
|
|
|
|
2017-12-18 21:54:03 +02:00
|
|
|
await fs.remove(resourceDir(id));
|
|
|
|
await fs.mkdirp(resourceDir(id), 0o755);
|
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
if (!synchronizers_[id]) {
|
2017-11-24 20:37:40 +02:00
|
|
|
const SyncTargetClass = SyncTargetRegistry.classById(syncTargetId_);
|
|
|
|
const syncTarget = new SyncTargetClass(db(id));
|
|
|
|
syncTarget.setFileApi(fileApi());
|
|
|
|
syncTarget.setLogger(logger);
|
|
|
|
synchronizers_[id] = await syncTarget.synchronizer();
|
2017-12-19 21:01:29 +02:00
|
|
|
synchronizers_[id].autoStartDecryptionWorker_ = false; // For testing we disable this since it would make the tests non-deterministic
|
2017-06-18 01:49:52 +02:00
|
|
|
}
|
2017-06-18 22:19:13 +02:00
|
|
|
|
2017-12-19 21:01:29 +02:00
|
|
|
encryptionServices_[id] = new EncryptionService();
|
|
|
|
decryptionWorkers_[id] = new DecryptionWorker();
|
|
|
|
decryptionWorkers_[id].setEncryptionService(encryptionServices_[id]);
|
2017-12-13 20:57:40 +02:00
|
|
|
|
2018-01-26 00:44:09 +02:00
|
|
|
await fileApi().clearRoot();
|
2017-06-14 21:59:02 +02:00
|
|
|
}
|
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
function db(id = null) {
|
|
|
|
if (id === null) id = currentClient_;
|
|
|
|
return databases_[id];
|
2017-06-14 21:59:02 +02:00
|
|
|
}
|
|
|
|
|
2017-06-18 22:19:13 +02:00
|
|
|
function synchronizer(id = null) {
|
|
|
|
if (id === null) id = currentClient_;
|
|
|
|
return synchronizers_[id];
|
2017-06-14 21:59:02 +02:00
|
|
|
}
|
|
|
|
|
2017-12-13 20:57:40 +02:00
|
|
|
function encryptionService(id = null) {
|
|
|
|
if (id === null) id = currentClient_;
|
|
|
|
return encryptionServices_[id];
|
|
|
|
}
|
|
|
|
|
2017-12-19 21:01:29 +02:00
|
|
|
function decryptionWorker(id = null) {
|
|
|
|
if (id === null) id = currentClient_;
|
|
|
|
return decryptionWorkers_[id];
|
|
|
|
}
|
|
|
|
|
2017-12-17 21:51:45 +02:00
|
|
|
async function loadEncryptionMasterKey(id = null, useExisting = false) {
|
2017-12-13 20:57:40 +02:00
|
|
|
const service = encryptionService(id);
|
|
|
|
|
2017-12-17 21:51:45 +02:00
|
|
|
let masterKey = null;
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
if (!useExisting) { // Create it
|
|
|
|
masterKey = await service.generateMasterKey('123456');
|
2017-12-17 21:51:45 +02:00
|
|
|
masterKey = await MasterKey.save(masterKey);
|
2018-03-09 22:59:12 +02:00
|
|
|
} else { // Use the one already available
|
2017-12-17 21:51:45 +02:00
|
|
|
materKey = await MasterKey.all();
|
2018-03-09 22:59:12 +02:00
|
|
|
if (!materKey.length) throw new Error('No mater key available');
|
2017-12-17 21:51:45 +02:00
|
|
|
masterKey = materKey[0];
|
|
|
|
}
|
2017-12-13 20:57:40 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
await service.loadMasterKey(masterKey, '123456', true);
|
2017-12-13 20:57:40 +02:00
|
|
|
|
|
|
|
return masterKey;
|
|
|
|
}
|
|
|
|
|
2017-06-14 21:59:02 +02:00
|
|
|
function fileApi() {
|
2017-06-18 22:19:13 +02:00
|
|
|
if (fileApi_) return fileApi_;
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
if (syncTargetId_ == SyncTargetRegistry.nameToId('filesystem')) {
|
|
|
|
fs.removeSync(syncDir)
|
2017-07-23 16:11:44 +02:00
|
|
|
fs.mkdirpSync(syncDir, 0o755);
|
|
|
|
fileApi_ = new FileApi(syncDir, new FileApiDriverLocal());
|
2018-03-09 22:59:12 +02:00
|
|
|
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('memory')) {
|
|
|
|
fileApi_ = new FileApi('/root', new FileApiDriverMemory());
|
|
|
|
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('nextcloud')) {
|
2018-01-25 23:15:58 +02:00
|
|
|
const options = {
|
2018-03-09 22:59:12 +02:00
|
|
|
baseUrl: () => 'http://nextcloud.local/remote.php/dav/files/admin/JoplinTest',
|
|
|
|
username: () => 'admin',
|
|
|
|
password: () => '123456',
|
2018-01-25 23:15:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const api = new WebDavApi(options);
|
2018-03-09 22:59:12 +02:00
|
|
|
fileApi_ = new FileApi('', new FileApiDriverWebDav(api));
|
2018-03-24 21:35:10 +02:00
|
|
|
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('dropbox')) {
|
|
|
|
const api = new DropboxApi();
|
|
|
|
const authTokenPath = __dirname + '/support/dropbox-auth.txt';
|
|
|
|
const authToken = fs.readFileSync(authTokenPath, 'utf8');
|
|
|
|
if (!authTokenPath) throw new Error('Dropbox auth token missing in ' + authTokenPath);
|
|
|
|
api.setAuthToken(authToken);
|
|
|
|
fileApi_ = new FileApi('', new FileApiDriverDropbox(api));
|
2017-07-24 20:58:11 +02:00
|
|
|
}
|
2018-01-25 23:15:58 +02:00
|
|
|
|
2017-07-24 20:58:11 +02:00
|
|
|
fileApi_.setLogger(logger);
|
|
|
|
fileApi_.setSyncTargetId(syncTargetId_);
|
2018-02-15 20:33:08 +02:00
|
|
|
fileApi_.requestRepeatCount_ = 0;
|
2017-07-24 20:58:11 +02:00
|
|
|
return fileApi_;
|
2017-06-14 21:59:02 +02:00
|
|
|
}
|
|
|
|
|
2017-12-13 20:57:40 +02:00
|
|
|
function objectsEqual(o1, o2) {
|
|
|
|
if (Object.getOwnPropertyNames(o1).length !== Object.getOwnPropertyNames(o2).length) return false;
|
|
|
|
for (let n in o1) {
|
|
|
|
if (!o1.hasOwnProperty(n)) continue;
|
|
|
|
if (o1[n] !== o2[n]) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function checkThrowAsync(asyncFn) {
|
|
|
|
let hasThrown = false;
|
|
|
|
try {
|
|
|
|
await asyncFn();
|
|
|
|
} catch (error) {
|
|
|
|
hasThrown = true;
|
|
|
|
}
|
|
|
|
return hasThrown;
|
|
|
|
}
|
|
|
|
|
2017-12-18 21:54:03 +02:00
|
|
|
function fileContentEqual(path1, path2) {
|
2018-03-09 22:59:12 +02:00
|
|
|
const fs = require('fs-extra');
|
|
|
|
const content1 = fs.readFileSync(path1, 'base64');
|
|
|
|
const content2 = fs.readFileSync(path2, 'base64');
|
2017-12-18 21:54:03 +02:00
|
|
|
return content1 === content2;
|
|
|
|
}
|
|
|
|
|
2018-01-02 21:17:14 +02:00
|
|
|
// Wrap an async test in a try/catch block so that done() is always called
|
|
|
|
// and display a proper error message instead of "unhandled promise error"
|
|
|
|
function asyncTest(callback) {
|
|
|
|
return async function(done) {
|
|
|
|
try {
|
|
|
|
await callback();
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
done();
|
2018-03-09 22:59:12 +02:00
|
|
|
}
|
2018-01-02 21:17:14 +02:00
|
|
|
}
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
module.exports = { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest };
|