www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

dataDirectoryTest.js (13751B)


      1 "use strict";
      2 
      3 describe("Zotero.DataDirectory", function () {
      4 	var tmpDir, oldDir, newDir, dbFilename, oldDBFile, newDBFile, oldStorageDir, newStorageDir,
      5 		oldTranslatorsDir, newTranslatorsDir, translatorName1, translatorName2,
      6 		oldStorageDir1, newStorageDir1, storageFile1, oldStorageDir2, newStorageDir2, storageFile2,
      7 		str1, str2, str3, str4, str5, str6,
      8 		oldMigrationMarker, newMigrationMarker,
      9 		stubs = {};
     10 	
     11 	before(function* () {
     12 		tmpDir = yield getTempDirectory();
     13 		oldDir = OS.Path.join(tmpDir, "old");
     14 		newDir = OS.Path.join(tmpDir, "new");
     15 		dbFilename = Zotero.DataDirectory.getDatabaseFilename();
     16 		oldDBFile = OS.Path.join(oldDir, dbFilename);
     17 		newDBFile = OS.Path.join(newDir, dbFilename);
     18 		oldStorageDir = OS.Path.join(oldDir, "storage");
     19 		newStorageDir = OS.Path.join(newDir, "storage");
     20 		oldTranslatorsDir = OS.Path.join(oldDir, "translators");
     21 		newTranslatorsDir = OS.Path.join(newDir, "translators");
     22 		translatorName1 = 'a.js';
     23 		translatorName2 = 'b.js';
     24 		oldStorageDir1 = OS.Path.join(oldStorageDir, 'AAAAAAAA');
     25 		newStorageDir1 = OS.Path.join(newStorageDir, 'AAAAAAAA');
     26 		storageFile1 = 'test.pdf';
     27 		oldStorageDir2 = OS.Path.join(oldStorageDir, 'BBBBBBBB');
     28 		newStorageDir2 = OS.Path.join(newStorageDir, 'BBBBBBBB');
     29 		storageFile2 = 'test.html';
     30 		str1 = '1';
     31 		str2 = '2';
     32 		str3 = '3';
     33 		str4 = '4';
     34 		str5 = '5';
     35 		str6 = '6';
     36 		oldMigrationMarker = OS.Path.join(oldDir, Zotero.DataDirectory.MIGRATION_MARKER);
     37 		newMigrationMarker = OS.Path.join(newDir, Zotero.DataDirectory.MIGRATION_MARKER);
     38 		
     39 		stubs.canMigrate = sinon.stub(Zotero.DataDirectory, "canMigrate").returns(true);
     40 		// A pipe always exists during tests, since Zotero is running
     41 		stubs.pipeExists = sinon.stub(Zotero.IPC, "pipeExists").returns(Zotero.Promise.resolve(false));
     42 		stubs.setDataDir = sinon.stub(Zotero.DataDirectory, "set");
     43 		stubs.isNewDirOnDifferentDrive = sinon.stub(Zotero.DataDirectory, 'isNewDirOnDifferentDrive').resolves(true);
     44 	});
     45 	
     46 	beforeEach(function* () {
     47 		stubs.setDataDir.reset();
     48 	});
     49 	
     50 	afterEach(function* () {
     51 		yield removeDir(oldDir);
     52 		yield removeDir(newDir);
     53 		Zotero.DataDirectory._cache(false);
     54 		yield Zotero.DataDirectory.init();
     55 	});
     56 	
     57 	after(function* () {
     58 		for (let key in stubs) {
     59 			try {
     60 				stubs[key].restore();
     61 			} catch(e) {}
     62 		}
     63 	});
     64 	
     65 	// Force non-mv mode
     66 	var disableCommandMode = function () {
     67 		if (!stubs.canMoveDirectoryWithCommand) {
     68 			stubs.canMoveDirectoryWithCommand = sinon.stub(Zotero.File, "canMoveDirectoryWithCommand")
     69 				.returns(false);
     70 		}
     71 	};
     72 	
     73 	// Force non-OS.File.move() mode
     74 	var disableFunctionMode = function () {
     75 		if (!stubs.canMoveDirectoryWithFunction) {
     76 			stubs.canMoveDirectoryWithFunction = sinon.stub(Zotero.File, "canMoveDirectoryWithFunction")
     77 				.returns(false);
     78 		}
     79 	};
     80 	
     81 	var resetCommandMode = function () {
     82 		if (stubs.canMoveDirectoryWithCommand) {
     83 			stubs.canMoveDirectoryWithCommand.restore();
     84 			stubs.canMoveDirectoryWithCommand = undefined;
     85 		}
     86 	};
     87 	
     88 	var resetFunctionMode = function () {
     89 		if (stubs.canMoveDirectoryWithFunction) {
     90 			stubs.canMoveDirectoryWithFunction.restore();
     91 			stubs.canMoveDirectoryWithFunction = undefined;
     92 		}
     93 	};
     94 	
     95 	var populateDataDirectory = Zotero.Promise.coroutine(function* (dir, srcDir, automatic = false) {
     96 		yield OS.File.makeDir(dir, { unixMode: 0o755 });
     97 		let storageDir = OS.Path.join(dir, 'storage');
     98 		let storageDir1 = OS.Path.join(storageDir, 'AAAAAAAA');
     99 		let storageDir2 = OS.Path.join(storageDir, 'BBBBBBBB');
    100 		let translatorsDir = OS.Path.join(dir, 'translators');
    101 		let migrationMarker = OS.Path.join(dir, Zotero.DataDirectory.MIGRATION_MARKER);
    102 		
    103 		// Database
    104 		yield Zotero.File.putContentsAsync(OS.Path.join(dir, dbFilename), str1);
    105 		// Database backup
    106 		yield Zotero.File.putContentsAsync(OS.Path.join(dir, dbFilename + '.bak'), str2);
    107 		// 'storage' directory
    108 		yield OS.File.makeDir(storageDir, { unixMode: 0o755 });
    109 		// 'storage' folders
    110 		yield OS.File.makeDir(storageDir1, { unixMode: 0o755 });
    111 		yield Zotero.File.putContentsAsync(OS.Path.join(storageDir1, storageFile1), str2);
    112 		yield OS.File.makeDir(storageDir2, { unixMode: 0o755 });
    113 		yield Zotero.File.putContentsAsync(OS.Path.join(storageDir2, storageFile2), str3);
    114 		// 'translators' and some translators
    115 		yield OS.File.makeDir(translatorsDir, { unixMode: 0o755 });
    116 		yield Zotero.File.putContentsAsync(OS.Path.join(translatorsDir, translatorName1), str4);
    117 		yield Zotero.File.putContentsAsync(OS.Path.join(translatorsDir, translatorName2), str5);
    118 		// Migration marker
    119 		yield Zotero.File.putContentsAsync(
    120 			migrationMarker,
    121 			JSON.stringify({
    122 				sourceDir: srcDir || dir,
    123 				automatic
    124 			})
    125 		);
    126 	});
    127 	
    128 	var checkMigration = Zotero.Promise.coroutine(function* (options = {}) {
    129 		if (!options.skipOldDir) {
    130 			assert.isFalse(yield OS.File.exists(oldDir));
    131 		}
    132 		yield assert.eventually.equal(Zotero.File.getContentsAsync(newDBFile), str1);
    133 		yield assert.eventually.equal(Zotero.File.getContentsAsync(newDBFile + '.bak'), str2);
    134 		if (!options.skipStorageFile1) {
    135 			yield assert.eventually.equal(
    136 				Zotero.File.getContentsAsync(OS.Path.join(newStorageDir1, storageFile1)), str2
    137 			);
    138 		}
    139 		yield assert.eventually.equal(
    140 			Zotero.File.getContentsAsync(OS.Path.join(newStorageDir2, storageFile2)), str3
    141 		);
    142 		yield assert.eventually.equal(
    143 			Zotero.File.getContentsAsync(OS.Path.join(newTranslatorsDir, translatorName1)), str4
    144 		);
    145 		yield assert.eventually.equal(
    146 			Zotero.File.getContentsAsync(OS.Path.join(newTranslatorsDir, translatorName2)), str5
    147 		);
    148 		if (!options.skipNewMarker) {
    149 			assert.isFalse(yield OS.File.exists(newMigrationMarker));
    150 		}
    151 		
    152 		if (!options.skipSetDataDirectory) {
    153 			assert.ok(stubs.setDataDir.calledOnce);
    154 			assert.ok(stubs.setDataDir.calledWith(newDir));
    155 		}
    156 	});
    157 	
    158 	
    159 	describe("#checkForMigration()", function () {
    160 		let fileMoveStub;
    161 		
    162 		beforeEach(function () {
    163 			disableCommandMode();
    164 			disableFunctionMode();
    165 		});
    166 		
    167 		after(function () {
    168 			resetCommandMode();
    169 			resetFunctionMode();
    170 		});
    171 		
    172 		var tests = [];
    173 		function add(desc, fn) {
    174 			tests.push([desc, fn]);
    175 		}
    176 		
    177 		it("should skip automatic migration if target directory exists and is non-empty", function* () {
    178 			resetCommandMode();
    179 			resetFunctionMode();
    180 			
    181 			yield populateDataDirectory(oldDir);
    182 			yield OS.File.remove(oldMigrationMarker);
    183 			yield OS.File.makeDir(newDir, { unixMode: 0o755 });
    184 			yield Zotero.File.putContentsAsync(OS.Path.join(newDir, 'a'), '');
    185 			
    186 			yield assert.eventually.isFalse(Zotero.DataDirectory.checkForMigration(oldDir, newDir));
    187 		});
    188 		
    189 		it("should skip automatic migration and show prompt if target directory is on a different drive", function* () {
    190 			resetCommandMode();
    191 			resetFunctionMode();
    192 			
    193 			yield populateDataDirectory(oldDir);
    194 			yield OS.File.remove(oldMigrationMarker);
    195 			
    196 			stubs.isNewDirOnDifferentDrive.resolves(true);
    197 			
    198 			var promise = waitForDialog(function (dialog) {
    199 				assert.include(
    200 					dialog.document.documentElement.textContent,
    201 					Zotero.getString(`dataDir.migration.failure.full.automatic.newDirOnDifferentDrive`, Zotero.clientName)
    202 				);
    203 			}, 'cancel');
    204 			
    205 			yield assert.eventually.isNotOk(Zotero.DataDirectory.checkForMigration(oldDir, newDir));
    206 			yield promise;
    207 
    208 			stubs.isNewDirOnDifferentDrive.resolves(false);
    209 		});
    210 		
    211 		add("should show error on partial failure", function (automatic) {
    212 			return function* () {
    213 				yield populateDataDirectory(oldDir, null, automatic);
    214 				
    215 				let origFunc = OS.File.move;
    216 				let fileMoveStub = sinon.stub(OS.File, "move").callsFake(function () {
    217 					if (OS.Path.basename(arguments[0]) == storageFile1) {
    218 						return Zotero.Promise.reject(new Error("Error"));
    219 					}
    220 					else {
    221 						return origFunc(...arguments);
    222 					}
    223 				});
    224 				let stub1 = sinon.stub(Zotero.File, "reveal").returns(Zotero.Promise.resolve());
    225 				let stub2 = sinon.stub(Zotero.Utilities.Internal, "quitZotero");
    226 				
    227 				var promise2;
    228 				// Click "Try Again" the first time, and then "Show Directories and Quit Zotero"
    229 				var promise = waitForDialog(function (dialog) {
    230 					promise2 = waitForDialog(null, 'extra1');
    231 					
    232 					// Make sure we're displaying the right message for this mode (automatic or manual)
    233 					Components.utils.import("resource://zotero/config.js");
    234 					assert.include(
    235 						dialog.document.documentElement.textContent,
    236 						Zotero.getString(
    237 							`dataDir.migration.failure.partial.${automatic ? 'automatic' : 'manual'}.text`,
    238 							[ZOTERO_CONFIG.CLIENT_NAME, Zotero.appName]
    239 						)
    240 					);
    241 				});
    242 				yield Zotero.DataDirectory.checkForMigration(oldDir, newDir);
    243 				yield promise;
    244 				yield promise2;
    245 				
    246 				assert.isTrue(stub1.calledTwice);
    247 				assert.isTrue(stub1.getCall(0).calledWith(oldStorageDir));
    248 				assert.isTrue(stub1.getCall(1).calledWith(newDBFile));
    249 				assert.isTrue(stub2.called);
    250 				
    251 				fileMoveStub.restore();
    252 				stub1.restore();
    253 				stub2.restore();
    254 			};
    255 		});
    256 		
    257 		add("should show error on full failure", function (automatic) {
    258 			return function* () {
    259 				yield populateDataDirectory(oldDir, null, automatic);
    260 				
    261 				let origFunc = OS.File.move;
    262 				let stub1 = sinon.stub(OS.File, "move").callsFake(function () {
    263 					if (OS.Path.basename(arguments[0]) == dbFilename) {
    264 						return Zotero.Promise.reject(new Error("Error"));
    265 					}
    266 					else {
    267 						return origFunc(...arguments);
    268 					}
    269 				});
    270 				let stub2 = sinon.stub(Zotero.File, "reveal").returns(Zotero.Promise.resolve());
    271 				let stub3 = sinon.stub(Zotero.Utilities.Internal, "quitZotero");
    272 				
    273 				var promise = waitForDialog(function (dialog) {
    274 					// Make sure we're displaying the right message for this mode (automatic or manual)
    275 					Components.utils.import("resource://zotero/config.js");
    276 					assert.include(
    277 						dialog.document.documentElement.textContent,
    278 						Zotero.getString(
    279 							`dataDir.migration.failure.full.${automatic ? 'automatic' : 'manual'}.text1`,
    280 							ZOTERO_CONFIG.CLIENT_NAME
    281 						)
    282 					);
    283 				});
    284 				yield Zotero.DataDirectory.checkForMigration(oldDir, newDir);
    285 				yield promise;
    286 				
    287 				assert.isTrue(stub2.calledOnce);
    288 				assert.isTrue(stub2.calledWith(oldDir));
    289 				assert.isTrue(stub3.called);
    290 				
    291 				stub1.restore();
    292 				stub2.restore();
    293 				stub3.restore();
    294 			};
    295 		});
    296 		
    297 		describe("automatic mode", function () {
    298 			tests.forEach(arr => {
    299 				it(arr[0], arr[1](true));
    300 			});
    301 		});
    302 		
    303 		describe("manual mode", function () {
    304 			tests.forEach(arr => {
    305 				it(arr[0], arr[1](false));
    306 			});
    307 		});
    308 		
    309 		it("should remove marker if old directory doesn't exist", function* () {
    310 			yield populateDataDirectory(newDir, oldDir);
    311 			yield Zotero.DataDirectory.checkForMigration(newDir, newDir);
    312 			yield checkMigration({
    313 				skipSetDataDirectory: true
    314 			});
    315 		});
    316 	});
    317 	
    318 	
    319 	describe("#migrate()", function () {
    320 		// Define tests and store for running in non-mv mode
    321 		var tests = [];
    322 		function add(desc, fn) {
    323 			it(desc, fn);
    324 			tests.push([desc, fn]);
    325 		}
    326 		
    327 		add("should move all files and folders", function* () {
    328 			yield populateDataDirectory(oldDir);
    329 			yield Zotero.DataDirectory.migrate(oldDir, newDir);
    330 			yield checkMigration();
    331 		});
    332 		
    333 		add("should resume partial migration with just marker copied", function* () {
    334 			yield populateDataDirectory(oldDir);
    335 			yield OS.File.makeDir(newDir, { unixMode: 0o755 });
    336 			
    337 			yield OS.File.copy(oldMigrationMarker, newMigrationMarker);
    338 			
    339 			yield Zotero.DataDirectory.migrate(oldDir, newDir, true);
    340 			yield checkMigration();
    341 		});
    342 		
    343 		add("should resume partial migration with database moved", function* () {
    344 			yield populateDataDirectory(oldDir);
    345 			yield OS.File.makeDir(newDir, { unixMode: 0o755 });
    346 			
    347 			yield OS.File.copy(oldMigrationMarker, newMigrationMarker);
    348 			yield OS.File.move(OS.Path.join(oldDir, dbFilename), OS.Path.join(newDir, dbFilename));
    349 			
    350 			yield Zotero.DataDirectory.migrate(oldDir, newDir, true);
    351 			yield checkMigration();
    352 		});
    353 		
    354 		add("should resume partial migration with some storage directories moved", function* () {
    355 			yield populateDataDirectory(oldDir);
    356 			yield populateDataDirectory(newDir, oldDir);
    357 			
    358 			// Moved: DB, DB backup, one storage dir
    359 			// Not moved: one storage dir, translators dir
    360 			yield OS.File.remove(oldDBFile);
    361 			yield OS.File.remove(oldDBFile + '.bak');
    362 			yield removeDir(oldStorageDir1);
    363 			yield removeDir(newTranslatorsDir);
    364 			yield removeDir(newStorageDir2);
    365 			
    366 			yield Zotero.DataDirectory.migrate(oldDir, newDir, true);
    367 			yield checkMigration();
    368 		});
    369 		
    370 		// Run all tests again without using mv
    371 		//
    372 		// On Windows these will just be duplicates of the above tests.
    373 		describe("non-mv mode", function () {
    374 			tests.forEach(arr => {
    375 				it(arr[0] + " [non-mv]", arr[1]);
    376 			});
    377 			
    378 			before(function () {
    379 				disableCommandMode();
    380 				disableFunctionMode();
    381 			});
    382 			
    383 			after(function () {
    384 				resetCommandMode();
    385 				resetFunctionMode();
    386 			});
    387 			
    388 			it("should handle partial failure", function* () {
    389 				yield populateDataDirectory(oldDir);
    390 				
    391 				let origFunc = OS.File.move;
    392 				let stub1 = sinon.stub(OS.File, "move").callsFake(function () {
    393 					if (OS.Path.basename(arguments[0]) == storageFile1) {
    394 						return Zotero.Promise.reject(new Error("Error"));
    395 					}
    396 					else {
    397 						return origFunc(...arguments);
    398 					}
    399 				});
    400 				
    401 				yield Zotero.DataDirectory.migrate(oldDir, newDir);
    402 				
    403 				stub1.restore();
    404 				
    405 				yield checkMigration({
    406 					skipOldDir: true,
    407 					skipStorageFile1: true,
    408 					skipNewMarker: true
    409 				});
    410 				
    411 				assert.isTrue(yield OS.File.exists(OS.Path.join(oldStorageDir1, storageFile1)));
    412 				assert.isFalse(yield OS.File.exists(OS.Path.join(oldStorageDir2, storageFile2)));
    413 				assert.isFalse(yield OS.File.exists(oldTranslatorsDir));
    414 				assert.isTrue(yield OS.File.exists(newMigrationMarker));
    415 			});
    416 		});
    417 	});
    418 });