file-recovery-service.test.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. const { dialog } = require('electron');
  2. const FileRecoveryService = require('../../src/main-process/file-recovery-service');
  3. const fs = require('fs-plus');
  4. const fsreal = require('fs');
  5. const EventEmitter = require('events').EventEmitter;
  6. const { assert } = require('chai');
  7. const sinon = require('sinon');
  8. const { escapeRegExp } = require('underscore-plus');
  9. const temp = require('temp').track();
  10. describe('FileRecoveryService', function() {
  11. let recoveryService, recoveryDirectory, spies;
  12. this.timeout(10 * 1000);
  13. beforeEach(() => {
  14. recoveryDirectory = temp.mkdirSync('atom-spec-file-recovery');
  15. recoveryService = new FileRecoveryService(recoveryDirectory);
  16. spies = sinon.createSandbox();
  17. });
  18. afterEach(() => {
  19. spies.restore();
  20. try {
  21. temp.cleanupSync();
  22. } catch (e) {
  23. // Ignore
  24. }
  25. });
  26. describe('when no crash happens during a save', () => {
  27. it('creates a recovery file and deletes it after saving', async () => {
  28. const mockWindow = {};
  29. const filePath = temp.path();
  30. fs.writeFileSync(filePath, 'some content');
  31. await recoveryService.willSavePath(mockWindow, filePath);
  32. assert.equal(fs.listTreeSync(recoveryDirectory).length, 1);
  33. fs.writeFileSync(filePath, 'changed');
  34. await recoveryService.didSavePath(mockWindow, filePath);
  35. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  36. assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed');
  37. fs.removeSync(filePath);
  38. });
  39. it('creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it', async () => {
  40. const mockWindow = {};
  41. const anotherMockWindow = {};
  42. const filePath = temp.path();
  43. fs.writeFileSync(filePath, 'some content');
  44. await recoveryService.willSavePath(mockWindow, filePath);
  45. await recoveryService.willSavePath(anotherMockWindow, filePath);
  46. assert.equal(fs.listTreeSync(recoveryDirectory).length, 1);
  47. fs.writeFileSync(filePath, 'changed');
  48. await recoveryService.didSavePath(mockWindow, filePath);
  49. assert.equal(fs.listTreeSync(recoveryDirectory).length, 1);
  50. assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed');
  51. await recoveryService.didSavePath(anotherMockWindow, filePath);
  52. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  53. assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed');
  54. fs.removeSync(filePath);
  55. });
  56. });
  57. describe('when a crash happens during a save', () => {
  58. it('restores the created recovery file and deletes it', async () => {
  59. const mockWindow = {};
  60. const filePath = temp.path();
  61. fs.writeFileSync(filePath, 'some content');
  62. await recoveryService.willSavePath(mockWindow, filePath);
  63. assert.equal(fs.listTreeSync(recoveryDirectory).length, 1);
  64. fs.writeFileSync(filePath, 'changed');
  65. await recoveryService.didCrashWindow(mockWindow);
  66. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  67. assert.equal(fs.readFileSync(filePath, 'utf8'), 'some content');
  68. fs.removeSync(filePath);
  69. });
  70. it('restores the created recovery file when many windows attempt to save the same file and one of them crashes', async () => {
  71. const mockWindow = {};
  72. const anotherMockWindow = {};
  73. const filePath = temp.path();
  74. fs.writeFileSync(filePath, 'A');
  75. await recoveryService.willSavePath(mockWindow, filePath);
  76. fs.writeFileSync(filePath, 'B');
  77. await recoveryService.willSavePath(anotherMockWindow, filePath);
  78. assert.equal(fs.listTreeSync(recoveryDirectory).length, 1);
  79. fs.writeFileSync(filePath, 'C');
  80. await recoveryService.didCrashWindow(mockWindow);
  81. assert.equal(fs.readFileSync(filePath, 'utf8'), 'A');
  82. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  83. fs.writeFileSync(filePath, 'D');
  84. await recoveryService.willSavePath(mockWindow, filePath);
  85. fs.writeFileSync(filePath, 'E');
  86. await recoveryService.willSavePath(anotherMockWindow, filePath);
  87. assert.equal(fs.listTreeSync(recoveryDirectory).length, 1);
  88. fs.writeFileSync(filePath, 'F');
  89. await recoveryService.didCrashWindow(anotherMockWindow);
  90. assert.equal(fs.readFileSync(filePath, 'utf8'), 'D');
  91. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  92. fs.removeSync(filePath);
  93. });
  94. it("emits a warning when a file can't be recovered", async () => {
  95. const mockWindow = {};
  96. const filePath = temp.path();
  97. fs.writeFileSync(filePath, 'content');
  98. let logs = [];
  99. spies.stub(console, 'log').callsFake(message => logs.push(message));
  100. spies.stub(dialog, 'showMessageBox');
  101. // Copy files to be recovered before mocking fs.createWriteStream
  102. await recoveryService.willSavePath(mockWindow, filePath);
  103. // Stub out fs.createWriteStream so that we can return a fake error when
  104. // attempting to copy the recovered file to its original location
  105. var fakeEmitter = new EventEmitter();
  106. var onStub = spies.stub(fakeEmitter, 'on');
  107. onStub
  108. .withArgs('error')
  109. .yields(new Error('Nope'))
  110. .returns(fakeEmitter);
  111. onStub.withArgs('open').returns(fakeEmitter);
  112. spies
  113. .stub(fsreal, 'createWriteStream')
  114. .withArgs(filePath)
  115. .returns(fakeEmitter);
  116. await recoveryService.didCrashWindow(mockWindow);
  117. let recoveryFiles = fs.listTreeSync(recoveryDirectory);
  118. assert.equal(recoveryFiles.length, 1);
  119. assert.equal(logs.length, 1);
  120. assert.match(logs[0], new RegExp(escapeRegExp(filePath)));
  121. assert.match(logs[0], new RegExp(escapeRegExp(recoveryFiles[0])));
  122. fs.removeSync(filePath);
  123. });
  124. });
  125. it("doesn't create a recovery file when the file that's being saved doesn't exist yet", async () => {
  126. const mockWindow = {};
  127. await recoveryService.willSavePath(mockWindow, 'a-file-that-doesnt-exist');
  128. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  129. await recoveryService.didSavePath(mockWindow, 'a-file-that-doesnt-exist');
  130. assert.equal(fs.listTreeSync(recoveryDirectory).length, 0);
  131. });
  132. });