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 });