storageLocalTest.js (23884B)
1 "use strict"; 2 3 describe("Zotero.Sync.Storage.Local", function () { 4 beforeEach(function* () { 5 yield resetDB({ 6 thisArg: this, 7 skipBundledFiles: true 8 }) 9 }) 10 11 12 describe("#checkForUpdatedFiles()", function () { 13 it("should flag modified file for upload and return it", function* () { 14 // Create attachment 15 let item = yield importTextAttachment(); 16 var hash = yield item.attachmentHash; 17 // Set file mtime to the past (without milliseconds, which aren't used on OS X) 18 var mtime = (Math.floor(new Date().getTime() / 1000) * 1000) - 1000; 19 yield OS.File.setDates((yield item.getFilePathAsync()), null, mtime); 20 21 // Mark as synced, so it will be checked 22 item.attachmentSyncedModificationTime = mtime; 23 item.attachmentSyncedHash = hash; 24 item.attachmentSyncState = "in_sync"; 25 yield item.saveTx({ skipAll: true }); 26 27 // Update mtime and contents 28 var path = yield item.getFilePathAsync(); 29 yield OS.File.setDates(path); 30 yield Zotero.File.putContentsAsync(path, Zotero.Utilities.randomString()); 31 32 // File should be returned 33 var libraryID = Zotero.Libraries.userLibraryID; 34 var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID); 35 36 yield item.eraseTx(); 37 38 assert.equal(changed, true); 39 assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD); 40 }) 41 42 it("should skip a file if mod time hasn't changed", function* () { 43 // Create attachment 44 let item = yield importTextAttachment(); 45 var hash = yield item.attachmentHash; 46 var mtime = yield item.attachmentModificationTime; 47 48 // Mark as synced, so it will be checked 49 item.attachmentSyncedModificationTime = mtime; 50 item.attachmentSyncedHash = hash; 51 item.attachmentSyncState = "in_sync"; 52 yield item.saveTx({ skipAll: true }); 53 54 var libraryID = Zotero.Libraries.userLibraryID; 55 var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID); 56 var syncState = item.attachmentSyncState; 57 58 yield item.eraseTx(); 59 60 assert.isFalse(changed); 61 assert.equal(syncState, Zotero.Sync.Storage.Local.SYNC_STATE_IN_SYNC); 62 }) 63 64 it("should skip a file if mod time has changed but contents haven't", function* () { 65 // Create attachment 66 let item = yield importTextAttachment(); 67 var hash = yield item.attachmentHash; 68 // Set file mtime to the past (without milliseconds, which aren't used on OS X) 69 var mtime = (Math.floor(new Date().getTime() / 1000) * 1000) - 1000; 70 yield OS.File.setDates((yield item.getFilePathAsync()), null, mtime); 71 72 // Mark as synced, so it will be checked 73 item.attachmentSyncedModificationTime = mtime; 74 item.attachmentSyncedHash = hash; 75 item.attachmentSyncState = "in_sync"; 76 yield item.saveTx({ skipAll: true }); 77 78 // Update mtime, but not contents 79 var path = yield item.getFilePathAsync(); 80 yield OS.File.setDates(path); 81 82 var libraryID = Zotero.Libraries.userLibraryID; 83 var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID); 84 var syncState = item.attachmentSyncState; 85 var syncedModTime = item.attachmentSyncedModificationTime; 86 var newModTime = yield item.attachmentModificationTime; 87 88 yield item.eraseTx(); 89 90 assert.isFalse(changed); 91 assert.equal(syncState, Zotero.Sync.Storage.Local.SYNC_STATE_IN_SYNC); 92 assert.equal(syncedModTime, newModTime); 93 }) 94 }) 95 96 describe("#updateSyncStates()", function () { 97 it("should update attachment sync states to 'to_upload'", function* () { 98 var attachment1 = yield importFileAttachment('test.png'); 99 attachment1.attachmentSyncState = 'in_sync'; 100 yield attachment1.saveTx(); 101 var attachment2 = yield importFileAttachment('test.png'); 102 attachment2.attachmentSyncState = 'in_sync'; 103 yield attachment2.saveTx(); 104 105 var local = Zotero.Sync.Storage.Local; 106 yield local.updateSyncStates([attachment1, attachment2], 'to_upload'); 107 108 for (let attachment of [attachment1, attachment2]) { 109 assert.strictEqual(attachment.attachmentSyncState, local.SYNC_STATE_TO_UPLOAD); 110 let state = yield Zotero.DB.valueQueryAsync( 111 "SELECT syncState FROM itemAttachments WHERE itemID=?", attachment.id 112 ); 113 assert.strictEqual(state, local.SYNC_STATE_TO_UPLOAD); 114 } 115 }); 116 }); 117 118 describe("#resetAllSyncStates()", function () { 119 it("should reset attachment sync states to 'to_upload'", function* () { 120 var attachment = yield importFileAttachment('test.png'); 121 attachment.attachmentSyncState = 'in_sync'; 122 yield attachment.saveTx(); 123 124 var local = Zotero.Sync.Storage.Local; 125 yield local.resetAllSyncStates(attachment.libraryID) 126 assert.strictEqual(attachment.attachmentSyncState, local.SYNC_STATE_TO_UPLOAD); 127 var state = yield Zotero.DB.valueQueryAsync( 128 "SELECT syncState FROM itemAttachments WHERE itemID=?", attachment.id 129 ); 130 assert.strictEqual(state, local.SYNC_STATE_TO_UPLOAD); 131 }); 132 }); 133 134 describe("#processDownload()", function () { 135 describe("single file", function () { 136 it("should download a single file into the attachment directory", function* () { 137 var libraryID = Zotero.Libraries.userLibraryID; 138 var parentItem = yield createDataObject('item'); 139 var key = Zotero.DataObjectUtilities.generateKey(); 140 var fileContents = Zotero.Utilities.randomString(); 141 142 var oldFilename = "Old File"; 143 var tmpDir = Zotero.getTempDirectory().path; 144 var tmpFile = OS.Path.join(tmpDir, key + '.tmp'); 145 yield Zotero.File.putContentsAsync(tmpFile, fileContents); 146 147 // Create an existing attachment directory to replace 148 var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path; 149 yield OS.File.makeDir( 150 dir, 151 { 152 unixMode: 0o755 153 } 154 ); 155 yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), ''); 156 157 var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(tmpFile)); 158 var mtime = 1445667239000; 159 160 var json = { 161 key, 162 version: 10, 163 itemType: 'attachment', 164 linkMode: 'imported_url', 165 url: 'https://example.com/foo.txt', 166 filename: 'foo.txt', 167 contentType: 'text/plain', 168 charset: 'utf-8', 169 md5, 170 mtime 171 }; 172 yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]); 173 174 var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key); 175 yield Zotero.Sync.Storage.Local.processDownload({ 176 item, 177 md5, 178 mtime 179 }); 180 yield OS.File.remove(tmpFile); 181 182 var storageDir = Zotero.Attachments.getStorageDirectory(item).path; 183 184 // Make sure previous files don't exist 185 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename))); 186 187 // Make sure main file matches attachment hash and mtime 188 yield assert.eventually.equal( 189 item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents) 190 ); 191 yield assert.eventually.equal(item.attachmentModificationTime, mtime); 192 }); 193 194 195 it("should download and rename a single file with invalid filename into the attachment directory", function* () { 196 var libraryID = Zotero.Libraries.userLibraryID; 197 var parentItem = yield createDataObject('item'); 198 var key = Zotero.DataObjectUtilities.generateKey(); 199 var fileContents = Zotero.Utilities.randomString(); 200 201 var oldFilename = "Old File"; 202 var newFilename = " ab — c \\:.txt."; 203 var filteredFilename = " ab — c .txt."; 204 var tmpDir = Zotero.getTempDirectory().path; 205 var tmpFile = OS.Path.join(tmpDir, key + '.tmp'); 206 yield Zotero.File.putContentsAsync(tmpFile, fileContents); 207 208 // Create an existing attachment directory to replace 209 var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path; 210 yield OS.File.makeDir( 211 dir, 212 { 213 unixMode: 0o755 214 } 215 ); 216 yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), ''); 217 218 var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(tmpFile)); 219 var mtime = 1445667239000; 220 221 var json = { 222 key, 223 version: 10, 224 itemType: 'attachment', 225 linkMode: 'imported_url', 226 url: 'https://example.com/foo.txt', 227 filename: newFilename, 228 contentType: 'text/plain', 229 charset: 'utf-8', 230 md5, 231 mtime 232 }; 233 yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]); 234 235 var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key); 236 yield Zotero.Sync.Storage.Local.processDownload({ 237 item, 238 md5, 239 mtime 240 }); 241 yield OS.File.remove(tmpFile); 242 243 var storageDir = Zotero.Attachments.getStorageDirectory(item).path; 244 245 // Make sure previous file doesn't exist 246 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename))); 247 // And new one does 248 assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, filteredFilename))); 249 250 // Make sure main file matches attachment hash and mtime 251 yield assert.eventually.equal( 252 item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents) 253 ); 254 yield assert.eventually.equal(item.attachmentModificationTime, mtime); 255 }); 256 257 258 it("should download and rename a single file with invalid filename using Windows parsing rules into the attachment directory", function* () { 259 var libraryID = Zotero.Libraries.userLibraryID; 260 var parentItem = yield createDataObject('item'); 261 var key = Zotero.DataObjectUtilities.generateKey(); 262 var fileContents = Zotero.Utilities.randomString(); 263 264 var oldFilename = "Old File"; 265 var newFilename = "a:b.txt"; 266 var filteredFilename = "ab.txt"; 267 var tmpDir = Zotero.getTempDirectory().path; 268 var tmpFile = OS.Path.join(tmpDir, key + '.tmp'); 269 yield Zotero.File.putContentsAsync(tmpFile, fileContents); 270 271 // Create an existing attachment directory to replace 272 var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path; 273 yield OS.File.makeDir( 274 dir, 275 { 276 unixMode: 0o755 277 } 278 ); 279 yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), ''); 280 281 var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(tmpFile)); 282 var mtime = 1445667239000; 283 284 var json = { 285 key, 286 version: 10, 287 itemType: 'attachment', 288 linkMode: 'imported_url', 289 url: 'https://example.com/foo.txt', 290 filename: 'a:b.txt', 291 contentType: 'text/plain', 292 charset: 'utf-8', 293 md5, 294 mtime 295 }; 296 yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]); 297 298 var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key); 299 300 // Stub functions to simulate OS.Path.basename() behavior on Windows 301 var basenameOrigFunc = OS.Path.basename.bind(OS.Path); 302 var basenameStub = sinon.stub(OS.Path, "basename").callsFake((path) => { 303 // Split on colon 304 if (path.endsWith("a:b.txt")) { 305 return "b.txt"; 306 } 307 return basenameOrigFunc(path); 308 }); 309 var pathToFileOrigFunc = Zotero.File.pathToFile.bind(Zotero.File); 310 var pathToFileStub = sinon.stub(Zotero.File, "pathToFile").callsFake((path) => { 311 if (path.includes(":")) { 312 throw new Error("Path contains colon"); 313 } 314 return pathToFileOrigFunc(path); 315 }); 316 317 yield Zotero.Sync.Storage.Local.processDownload({ 318 item, 319 md5, 320 mtime 321 }); 322 yield OS.File.remove(tmpFile); 323 324 var storageDir = Zotero.Attachments.getStorageDirectory(item).path; 325 326 basenameStub.restore(); 327 pathToFileStub.restore(); 328 329 // Make sure path is set correctly 330 assert.equal(item.getFilePath(), OS.Path.join(storageDir, filteredFilename)); 331 // Make sure previous files don't exist 332 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename))); 333 // And new one does 334 assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, filteredFilename))); 335 336 // Make sure main file matches attachment hash and mtime 337 yield assert.eventually.equal( 338 item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents) 339 ); 340 yield assert.eventually.equal(item.attachmentModificationTime, mtime); 341 }); 342 }); 343 344 describe("ZIP", function () { 345 it("should download and extract a ZIP file into the attachment directory", function* () { 346 var file1Name = 'index.html'; 347 var file1Contents = '<html><body>Test</body></html>'; 348 var file2Name = 'aux1.txt'; 349 var file2Contents = 'Test 1'; 350 var subDirName = 'sub'; 351 var file3Name = 'aux2'; 352 var file3Contents = 'Test 2'; 353 354 var libraryID = Zotero.Libraries.userLibraryID; 355 var parentItem = yield createDataObject('item'); 356 var key = Zotero.DataObjectUtilities.generateKey(); 357 358 var tmpDir = Zotero.getTempDirectory().path; 359 var zipFile = OS.Path.join(tmpDir, key + '.tmp'); 360 361 // Create ZIP file with subdirectory 362 var tmpDir = Zotero.getTempDirectory().path; 363 var zipDir = yield getTempDirectory(); 364 yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, file1Name), file1Contents); 365 yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, file2Name), file2Contents); 366 var subDir = OS.Path.join(zipDir, subDirName); 367 yield OS.File.makeDir(subDir); 368 yield Zotero.File.putContentsAsync(OS.Path.join(subDir, file3Name), file3Contents); 369 yield Zotero.File.zipDirectory(zipDir, zipFile); 370 yield removeDir(zipDir); 371 372 // Create an existing attachment directory (and subdirectory) to replace 373 var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path; 374 yield OS.File.makeDir( 375 OS.Path.join(dir, 'subdir'), 376 { 377 from: Zotero.DataDirectory.dir, 378 unixMode: 0o755 379 } 380 ); 381 yield Zotero.File.putContentsAsync(OS.Path.join(dir, 'A'), ''); 382 yield Zotero.File.putContentsAsync(OS.Path.join(dir, 'subdir', 'B'), ''); 383 384 var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(zipFile)); 385 var mtime = 1445667239000; 386 387 var json = { 388 key, 389 version: 10, 390 itemType: 'attachment', 391 linkMode: 'imported_url', 392 url: 'https://example.com', 393 filename: file1Name, 394 contentType: 'text/html', 395 charset: 'utf-8', 396 md5, 397 mtime 398 }; 399 yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]); 400 401 var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key); 402 yield Zotero.Sync.Storage.Local.processDownload({ 403 item, 404 md5, 405 mtime, 406 compressed: true 407 }); 408 yield OS.File.remove(zipFile); 409 410 var storageDir = Zotero.Attachments.getStorageDirectory(item).path; 411 412 // Make sure previous files don't exist 413 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, 'A'))); 414 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, 'subdir'))); 415 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, 'subdir', 'B'))); 416 417 // Make sure main file matches attachment hash and mtime 418 yield assert.eventually.equal( 419 item.attachmentHash, Zotero.Utilities.Internal.md5(file1Contents) 420 ); 421 yield assert.eventually.equal(item.attachmentModificationTime, mtime); 422 423 // Check second file 424 yield assert.eventually.equal( 425 Zotero.File.getContentsAsync(OS.Path.join(storageDir, file2Name)), 426 file2Contents 427 ); 428 429 // Check subdirectory and file 430 assert.isTrue((yield OS.File.stat(OS.Path.join(storageDir, subDirName))).isDir); 431 yield assert.eventually.equal( 432 Zotero.File.getContentsAsync(OS.Path.join(storageDir, subDirName, file3Name)), 433 file3Contents 434 ); 435 }); 436 437 438 it("should download and rename a ZIP file with invalid filename using Windows parsing rules into the attachment directory", function* () { 439 var libraryID = Zotero.Libraries.userLibraryID; 440 var parentItem = yield createDataObject('item'); 441 var key = Zotero.DataObjectUtilities.generateKey(); 442 443 var oldFilename = "Old File"; 444 var oldAuxFilename = "a.gif"; 445 var newFilename = "a:b.html"; 446 var fileContents = Zotero.Utilities.randomString(); 447 var newAuxFilename = "b.gif"; 448 var filteredFilename = "ab.html"; 449 var tmpDir = Zotero.getTempDirectory().path; 450 var zipFile = OS.Path.join(tmpDir, key + '.tmp'); 451 452 // Create ZIP file 453 var tmpDir = Zotero.getTempDirectory().path; 454 var zipDir = yield getTempDirectory(); 455 yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, newFilename), fileContents); 456 yield Zotero.File.putContentsAsync(OS.Path.join(zipDir, newAuxFilename), ''); 457 yield Zotero.File.zipDirectory(zipDir, zipFile); 458 yield removeDir(zipDir); 459 460 // Create an existing attachment directory to replace 461 var dir = Zotero.Attachments.getStorageDirectoryByLibraryAndKey(libraryID, key).path; 462 yield OS.File.makeDir( 463 dir, 464 { 465 unixMode: 0o755 466 } 467 ); 468 yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldFilename), ''); 469 yield Zotero.File.putContentsAsync(OS.Path.join(dir, oldAuxFilename), ''); 470 471 var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(zipFile)); 472 var mtime = 1445667239000; 473 474 var json = { 475 key, 476 version: 10, 477 itemType: 'attachment', 478 linkMode: 'imported_url', 479 url: 'https://example.com/foo.html', 480 filename: 'a:b.html', 481 contentType: 'text/plain', 482 charset: 'utf-8', 483 md5, 484 mtime 485 }; 486 yield Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]); 487 488 var item = yield Zotero.Items.getByLibraryAndKeyAsync(libraryID, key); 489 490 // Stub functions to simulate OS.Path.basename() behavior on Windows 491 var basenameOrigFunc = OS.Path.basename.bind(OS.Path); 492 var basenameStub = sinon.stub(OS.Path, "basename").callsFake((path) => { 493 // Split on colon 494 if (path.endsWith("a:b.html")) { 495 return "b.html"; 496 } 497 return basenameOrigFunc(path); 498 }); 499 var pathToFileOrigFunc = Zotero.File.pathToFile.bind(Zotero.File); 500 var pathToFileStub = sinon.stub(Zotero.File, "pathToFile").callsFake((path) => { 501 if (path.includes(":")) { 502 throw new Error("Path contains colon"); 503 } 504 return pathToFileOrigFunc(path); 505 }); 506 507 yield Zotero.Sync.Storage.Local.processDownload({ 508 item, 509 md5, 510 mtime, 511 compressed: true 512 }); 513 yield OS.File.remove(zipFile); 514 515 var storageDir = Zotero.Attachments.getStorageDirectory(item).path; 516 517 basenameStub.restore(); 518 pathToFileStub.restore(); 519 520 // Make sure path is set correctly 521 assert.equal(item.getFilePath(), OS.Path.join(storageDir, filteredFilename)); 522 // Make sure previous files don't exist 523 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldFilename))); 524 assert.isFalse(yield OS.File.exists(OS.Path.join(storageDir, oldAuxFilename))); 525 // And new ones do 526 assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, filteredFilename))); 527 assert.isTrue(yield OS.File.exists(OS.Path.join(storageDir, newAuxFilename))); 528 529 // Make sure main file matches attachment hash and mtime 530 yield assert.eventually.equal( 531 item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents) 532 ); 533 yield assert.eventually.equal(item.attachmentModificationTime, mtime); 534 }); 535 }); 536 }) 537 538 describe("#getConflicts()", function () { 539 it("should return an array of objects for attachments in conflict", function* () { 540 var libraryID = Zotero.Libraries.userLibraryID; 541 542 var item1 = yield importFileAttachment('test.png'); 543 item1.version = 10; 544 yield item1.saveTx(); 545 var item2 = yield importTextAttachment(); 546 var item3 = yield importHTMLAttachment(); 547 item3.version = 11; 548 yield item3.saveTx(); 549 550 var json1 = item1.toJSON(); 551 var json3 = item3.toJSON(); 552 // Change remote mtimes 553 // Round to nearest second because OS X doesn't support ms resolution 554 var now = Math.round(new Date().getTime() / 1000) * 1000; 555 json1.mtime = now - 10000; 556 json3.mtime = now - 20000; 557 yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1, json3]); 558 559 item1.attachmentSyncState = "in_conflict"; 560 yield item1.saveTx({ skipAll: true }); 561 item3.attachmentSyncState = "in_conflict"; 562 yield item3.saveTx({ skipAll: true }); 563 564 var conflicts = yield Zotero.Sync.Storage.Local.getConflicts(libraryID); 565 assert.lengthOf(conflicts, 2); 566 567 var item1Conflict = conflicts.find(x => x.left.key == item1.key); 568 assert.equal( 569 item1Conflict.left.dateModified, 570 Zotero.Date.dateToISO(new Date(yield item1.attachmentModificationTime)) 571 ); 572 assert.equal( 573 item1Conflict.right.dateModified, 574 Zotero.Date.dateToISO(new Date(json1.mtime)) 575 ); 576 577 var item3Conflict = conflicts.find(x => x.left.key == item3.key); 578 assert.equal( 579 item3Conflict.left.dateModified, 580 Zotero.Date.dateToISO(new Date(yield item3.attachmentModificationTime)) 581 ); 582 assert.equal( 583 item3Conflict.right.dateModified, 584 Zotero.Date.dateToISO(new Date(json3.mtime)) 585 ); 586 }) 587 }) 588 589 describe("#resolveConflicts()", function () { 590 var win; 591 592 before(function* () { 593 win = yield loadBrowserWindow(); 594 }); 595 596 after(function () { 597 if (win) { 598 win.close(); 599 } 600 }); 601 602 603 it("should show the conflict resolution window on attachment conflicts", function* () { 604 var libraryID = Zotero.Libraries.userLibraryID; 605 606 var item1 = yield importFileAttachment('test.png'); 607 item1.version = 10; 608 yield item1.saveTx(); 609 var item2 = yield importTextAttachment(); 610 var item3 = yield importHTMLAttachment(); 611 item3.version = 11; 612 yield item3.saveTx(); 613 614 var json1 = item1.toJSON(); 615 var json3 = item3.toJSON(); 616 // Change remote mtimes and hashes 617 json1.mtime = new Date().getTime() + 10000; 618 json1.md5 = 'f4ce1167f3a854896c257a0cc1ac387f'; 619 json3.mtime = new Date().getTime() - 10000; 620 json3.md5 = 'fcd080b1c2cad562237823ec27671bbd'; 621 yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1, json3]); 622 623 item1.attachmentSyncState = "in_conflict"; 624 yield item1.saveTx({ skipAll: true }); 625 item3.attachmentSyncState = "in_conflict"; 626 yield item3.saveTx({ skipAll: true }); 627 628 var promise = waitForWindow('chrome://zotero/content/merge.xul', function (dialog) { 629 var doc = dialog.document; 630 var wizard = doc.documentElement; 631 var mergeGroup = wizard.getElementsByTagName('zoteromergegroup')[0]; 632 633 // 1 (remote) 634 // Later remote version should be selected 635 assert.equal(mergeGroup.rightpane.getAttribute('selected'), 'true'); 636 637 // Check checkbox text 638 assert.equal( 639 doc.getElementById('resolve-all').label, 640 Zotero.getString('sync.conflict.resolveAllRemote') 641 ); 642 643 // Select local object 644 mergeGroup.leftpane.click(); 645 assert.equal(mergeGroup.leftpane.getAttribute('selected'), 'true'); 646 647 wizard.getButton('next').click(); 648 649 // 2 (local) 650 // Later local version should be selected 651 assert.equal(mergeGroup.leftpane.getAttribute('selected'), 'true'); 652 // Select remote object 653 mergeGroup.rightpane.click(); 654 assert.equal(mergeGroup.rightpane.getAttribute('selected'), 'true'); 655 656 if (Zotero.isMac) { 657 assert.isTrue(wizard.getButton('next').hidden); 658 assert.isFalse(wizard.getButton('finish').hidden); 659 } 660 else { 661 // TODO 662 } 663 wizard.getButton('finish').click(); 664 }) 665 yield Zotero.Sync.Storage.Local.resolveConflicts(libraryID); 666 yield promise; 667 668 assert.equal(item1.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD); 669 assert.equal(item1.attachmentSyncedModificationTime, json1.mtime); 670 assert.equal(item1.attachmentSyncedHash, json1.md5); 671 assert.equal(item3.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_DOWNLOAD); 672 assert.isNull(item3.attachmentSyncedModificationTime); 673 assert.isNull(item3.attachmentSyncedHash); 674 }) 675 }) 676 })