Mailspring/spec/stores/file-download-store-spec.coffee

309 lines
13 KiB
CoffeeScript
Raw Normal View History

fs = require 'fs'
path = require 'path'
{shell} = require 'electron'
NylasAPI = require '../../src/flux/nylas-api'
File = require '../../src/flux/models/file'
Message = require '../../src/flux/models/message'
FileDownloadStore = require '../../src/flux/stores/file-download-store'
2016-01-12 05:40:38 +08:00
AccountStore = require '../../src/flux/stores/account-store'
Download = FileDownloadStore.Download
describe "FileDownloadStore.Download", ->
beforeEach ->
spyOn(fs, 'createWriteStream')
spyOn(NylasAPI, 'makeRequest')
describe "constructor", ->
it "should require a non-empty filename", ->
expect(-> new Download(fileId: '123', targetPath: 'test.png')).toThrow()
expect(-> new Download(filename: null, fileId: '123', targetPath: 'test.png')).toThrow()
expect(-> new Download(filename: '', fileId: '123', targetPath: 'test.png')).toThrow()
it "should require a non-empty fileId", ->
expect(-> new Download(filename: 'test.png', fileId: null, targetPath: 'test.png')).toThrow()
expect(-> new Download(filename: 'test.png', fileId: '', targetPath: 'test.png')).toThrow()
it "should require a download path", ->
expect(-> new Download(filename: 'test.png', fileId: '123')).toThrow()
expect(-> new Download(filename: 'test.png', fileId: '123', targetPath: '')).toThrow()
describe "run", ->
beforeEach ->
account = AccountStore.accounts()[0]
@download = new Download(fileId: '123', targetPath: 'test.png', filename: 'test.png', accountId: account.id)
@download.run()
expect(NylasAPI.makeRequest).toHaveBeenCalled()
it "should create a request with a null encoding to prevent the request library from attempting to parse the (potentially very large) response", ->
expect(NylasAPI.makeRequest.mostRecentCall.args[0].json).toBe(false)
expect(NylasAPI.makeRequest.mostRecentCall.args[0].encoding).toBe(null)
it "should create a request for /files/123/download", ->
expect(NylasAPI.makeRequest.mostRecentCall.args[0].path).toBe("/files/123/download")
describe "FileDownloadStore", ->
beforeEach ->
account = AccountStore.accounts()[0]
spyOn(shell, 'showItemInFolder')
spyOn(shell, 'openItem')
@testfile = new File({
accountId: account.id,
filename: '123.png',
contentType: 'image/png',
id: "id",
size: 100
})
@testdownload = new Download({
accountId: account.id,
state : 'unknown',
fileId : 'id',
percent : 0,
filename : '123.png',
filesize : 100,
targetPath : '/Users/testuser/.nylas/downloads/id/123.png'
})
FileDownloadStore._downloads = {}
FileDownloadStore._downloadDirectory = "/Users/testuser/.nylas/downloads"
describe "pathForFile", ->
it "should return path within the download directory with the file id and displayName", ->
f = new File(filename: '123.png', contentType: 'image/png', id: 'id')
spyOn(f, 'displayName').andCallThrough()
expect(FileDownloadStore.pathForFile(f)).toBe("/Users/testuser/.nylas/downloads/id/123.png")
expect(f.displayName).toHaveBeenCalled()
it "should return unique paths for identical filenames with different IDs", ->
f1 = new File(filename: '123.png', contentType: 'image/png', id: 'id1')
f2 = new File(filename: '123.png', contentType: 'image/png', id: 'id2')
expect(FileDownloadStore.pathForFile(f1)).toBe("/Users/testuser/.nylas/downloads/id1/123.png")
expect(FileDownloadStore.pathForFile(f2)).toBe("/Users/testuser/.nylas/downloads/id2/123.png")
it "should escape the displayName if it contains path separator characters", ->
f1 = new File(filename: "static#{path.sep}b#{path.sep}a.jpg", contentType: 'image/png', id: 'id1')
expect(FileDownloadStore.pathForFile(f1)).toBe("/Users/testuser/.nylas/downloads/id1/static-b-a.jpg")
f1 = new File(filename: "my:file ? Windows /hates/ me :->.jpg", contentType: 'image/png', id: 'id1')
expect(FileDownloadStore.pathForFile(f1)).toBe("/Users/testuser/.nylas/downloads/id1/my-file - Windows -hates- me ---.jpg")
describe "_checkForDownloadedFile", ->
it "should return true if the file exists at the path and is the right size", ->
f = new File(filename: '123.png', contentType: 'image/png', id: "id", size: 100)
2015-08-29 04:37:09 +08:00
spyOn(fs, 'statAsync').andCallFake (path) ->
Promise.resolve({size: 100})
waitsForPromise ->
FileDownloadStore._checkForDownloadedFile(f).then (downloaded) ->
expect(downloaded).toBe(true)
it "should return false if the file does not exist", ->
f = new File(filename: '123.png', contentType: 'image/png', id: "id", size: 100)
2015-08-29 04:37:09 +08:00
spyOn(fs, 'statAsync').andCallFake (path) ->
Promise.reject(new Error("File does not exist"))
waitsForPromise ->
FileDownloadStore._checkForDownloadedFile(f).then (downloaded) ->
expect(downloaded).toBe(false)
it "should return false if the file is too small", ->
f = new File(filename: '123.png', contentType: 'image/png', id: "id", size: 100)
2015-08-29 04:37:09 +08:00
spyOn(fs, 'statAsync').andCallFake (path) ->
Promise.resolve({size: 50})
waitsForPromise ->
FileDownloadStore._checkForDownloadedFile(f).then (downloaded) ->
expect(downloaded).toBe(false)
describe "_newMailReceived", ->
it "should fetch attachments if the setting is on-receive", ->
spyOn(FileDownloadStore, '_fetch')
spyOn(NylasEnv.config, 'get').andCallFake (key) ->
return 'on-receive' if key is 'core.attachments.downloadPolicy'
return null
FileDownloadStore._newMailReceived(message: [new Message(files: [new File()])])
expect(FileDownloadStore._fetch).toHaveBeenCalled()
it "should not fetch attachments otherwise", ->
spyOn(FileDownloadStore, '_fetch')
spyOn(NylasEnv.config, 'get').andCallFake (key) ->
return 'on-read' if key is 'core.attachments.downloadPolicy'
return null
FileDownloadStore._newMailReceived(message: [new Message(files: [new File()])])
expect(FileDownloadStore._fetch).not.toHaveBeenCalled()
describe "_runDownload", ->
beforeEach ->
spyOn(FileDownloadStore.Download.prototype, 'run').andCallFake -> Promise.resolve(@)
spyOn(FileDownloadStore, '_prepareFolder').andCallFake -> Promise.resolve(true)
spyOn(FileDownloadStore, '_cleanupDownload')
it "should make sure that the download file path exists", ->
FileDownloadStore._runDownload(@testfile)
expect(FileDownloadStore._prepareFolder).toHaveBeenCalled()
it "should return the promise returned by download.run if the download already exists", ->
existing =
fileId: @testfile.id
run: jasmine.createSpy('existing.run').andCallFake ->
Promise.resolve(existing)
FileDownloadStore._downloads[@testfile.id] = existing
promise = FileDownloadStore._runDownload(@testfile)
expect(promise instanceof Promise).toBe(true)
waitsForPromise ->
promise.then ->
expect(existing.run).toHaveBeenCalled()
describe "when the downloaded file exists", ->
beforeEach ->
spyOn(FileDownloadStore, '_checkForDownloadedFile').andCallFake ->
Promise.resolve(true)
it "should resolve with a Download without calling download.run", ->
waitsForPromise =>
FileDownloadStore._runDownload(@testfile).then (download) ->
expect(FileDownloadStore.Download.prototype.run).not.toHaveBeenCalled()
expect(download instanceof FileDownloadStore.Download).toBe(true)
expect(download.data()).toEqual({
state : 'finished',
fileId : 'id',
percent : 0,
filename : '123.png',
filesize : 100,
targetPath : '/Users/testuser/.nylas/downloads/id/123.png'
})
describe "when the downloaded file does not exist", ->
beforeEach ->
spyOn(FileDownloadStore, '_checkForDownloadedFile').andCallFake ->
Promise.resolve(false)
it "should register the download with the right attributes", ->
FileDownloadStore._runDownload(@testfile)
advanceClock(0)
expect(FileDownloadStore.downloadDataForFile(@testfile.id)).toEqual({
state : 'unstarted',fileId : 'id',
percent : 0,
filename : '123.png',
filesize : 100,
targetPath : '/Users/testuser/.nylas/downloads/id/123.png'
})
it "should call download.run", ->
waitsForPromise =>
FileDownloadStore._runDownload(@testfile)
runs ->
expect(FileDownloadStore.Download.prototype.run).toHaveBeenCalled()
it "should resolve with a Download", ->
waitsForPromise =>
FileDownloadStore._runDownload(@testfile).then (download) ->
expect(download instanceof FileDownloadStore.Download).toBe(true)
expect(download.data()).toEqual({
state : 'unstarted',
fileId : 'id',
percent : 0,
filename : '123.png',
filesize : 100,
targetPath : '/Users/testuser/.nylas/downloads/id/123.png'
})
describe "_fetch", ->
it "should call through to startDownload", ->
spyOn(FileDownloadStore, '_runDownload').andCallFake ->
Promise.resolve(@testdownload)
FileDownloadStore._fetch(@testfile)
expect(FileDownloadStore._runDownload).toHaveBeenCalled()
it "should fail silently since it's called passively", ->
spyOn(FileDownloadStore, '_presentError')
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
Promise.reject(@testdownload)
FileDownloadStore._fetch(@testfile)
expect(FileDownloadStore._presentError).not.toHaveBeenCalled()
describe "_fetchAndOpen", ->
it "should open the file once it's been downloaded", ->
@savePath = "/Users/imaginary/.nylas/Downloads/a.png"
download = {targetPath: @savePath}
downloadResolve = null
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
new Promise (resolve, reject) ->
downloadResolve = resolve
FileDownloadStore._fetchAndOpen(@testfile)
expect(shell.openItem).not.toHaveBeenCalled()
downloadResolve(download)
advanceClock(100)
expect(shell.openItem).toHaveBeenCalledWith(@savePath)
it "should open an error if the download fails", ->
spyOn(FileDownloadStore, '_presentError')
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
Promise.reject(@testdownload)
FileDownloadStore._fetchAndOpen(@testfile)
advanceClock(1)
expect(FileDownloadStore._presentError).toHaveBeenCalled()
describe "_fetchAndSave", ->
beforeEach ->
@savePath = "/Users/imaginary/.nylas/Downloads/b.png"
refactor(env): new NylasEnv global Converted all references of global atom to NylasEnv Temporary rename atom.io find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.io/temporaryAtomIoReplacement/g' atom.config to NylasEnv.config find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.config/NylasEnv.config/g' atom.packages -> NylasEnv.packages atom.commands -> NylasEnv.commands atom.getLoadSettings find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.commands/NylasEnv.commands/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getLoadSettings/NylasEnv.getLoadSettings/g' More common atom methods find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.styles/NylasEnv.styles/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.emitError/NylasEnv.emitError/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.inSpecMode/NylasEnv.inSpecMode/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.inDevMode/NylasEnv.inDevMode/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getWindowType/NylasEnv.getWindowType/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.displayWindow/NylasEnv.displayWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.quit/NylasEnv.quit/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.close/NylasEnv.close/g' More atom method changes find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.keymaps/NylasEnv.keymaps/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.hide/NylasEnv.hide/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getCurrentWindow/NylasEnv.getCurrentWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.menu/NylasEnv.menu/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getConfigDirPath/NylasEnv.getConfigDirPath/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.isMainWindow/NylasEnv.isMainWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.finishUnload/NylasEnv.finishUnload/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.isWorkWindow/NylasEnv.isWorkWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.showSaveDialog/NylasEnv.showSaveDialog/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.append/NylasEnv.append/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.confirm/NylasEnv.confirm/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.clipboard/NylasEnv.clipboard/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getVersion/NylasEnv.getVersion/g' More atom renaming Rename atom methods More atom methods Fix grunt config variable Change atom.cmd to N1.cmd Rename atom.coffee and atom.js to nylas-env.coffee nylas-env.js Fix atom global reference in specs manually Fix atom requires Change engine from atom to nylas got rid of global/nylas-env rename to nylas-win-bootup Fix onWindowPropsChanged to onWindowPropsReceived fix nylas-workspace atom-text-editor to nylas-theme-wrap atom-text-editor -> nylas-theme-wrap Replacing atom keyword AtomWindow -> NylasWindow Replace Atom -> N1 Rename atom items nylas.asar -> atom.asar Remove more atom references Remove 6to5 references Remove license exception for atom
2015-11-12 02:25:11 +08:00
spyOn(NylasEnv, 'showSaveDialog').andCallFake (options, callback) => callback(@savePath)
it "should open a save dialog and prompt the user to choose a download path", ->
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
new Promise (resolve, reject) -> # never resolve
FileDownloadStore._fetchAndSave(@testfile)
refactor(env): new NylasEnv global Converted all references of global atom to NylasEnv Temporary rename atom.io find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.io/temporaryAtomIoReplacement/g' atom.config to NylasEnv.config find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.config/NylasEnv.config/g' atom.packages -> NylasEnv.packages atom.commands -> NylasEnv.commands atom.getLoadSettings find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.commands/NylasEnv.commands/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getLoadSettings/NylasEnv.getLoadSettings/g' More common atom methods find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.styles/NylasEnv.styles/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.emitError/NylasEnv.emitError/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.inSpecMode/NylasEnv.inSpecMode/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.inDevMode/NylasEnv.inDevMode/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getWindowType/NylasEnv.getWindowType/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.displayWindow/NylasEnv.displayWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.quit/NylasEnv.quit/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.close/NylasEnv.close/g' More atom method changes find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.keymaps/NylasEnv.keymaps/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.hide/NylasEnv.hide/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getCurrentWindow/NylasEnv.getCurrentWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.menu/NylasEnv.menu/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getConfigDirPath/NylasEnv.getConfigDirPath/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.isMainWindow/NylasEnv.isMainWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.finishUnload/NylasEnv.finishUnload/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.isWorkWindow/NylasEnv.isWorkWindow/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.showSaveDialog/NylasEnv.showSaveDialog/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.append/NylasEnv.append/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.confirm/NylasEnv.confirm/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.clipboard/NylasEnv.clipboard/g' find -E . -regex ".*\.(coffee|cjsx|js|md|cmd|es6)" -print0 | xargs -0 sed -i "" 's/atom.getVersion/NylasEnv.getVersion/g' More atom renaming Rename atom methods More atom methods Fix grunt config variable Change atom.cmd to N1.cmd Rename atom.coffee and atom.js to nylas-env.coffee nylas-env.js Fix atom global reference in specs manually Fix atom requires Change engine from atom to nylas got rid of global/nylas-env rename to nylas-win-bootup Fix onWindowPropsChanged to onWindowPropsReceived fix nylas-workspace atom-text-editor to nylas-theme-wrap atom-text-editor -> nylas-theme-wrap Replacing atom keyword AtomWindow -> NylasWindow Replace Atom -> N1 Rename atom items nylas.asar -> atom.asar Remove more atom references Remove 6to5 references Remove license exception for atom
2015-11-12 02:25:11 +08:00
expect(NylasEnv.showSaveDialog).toHaveBeenCalled()
expect(FileDownloadStore._runDownload).toHaveBeenCalledWith(@testfile)
it "should copy the file to the download path after it's been downloaded and open it after the stream has ended", ->
download = {targetPath: @savePath}
onEndEventCallback = null
streamStub =
pipe: ->
on: (eventName, eventCallback) =>
onEndEventCallback = eventCallback
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
Promise.resolve(download)
spyOn(fs, 'createReadStream').andReturn(streamStub)
spyOn(fs, 'createWriteStream')
FileDownloadStore._fetchAndSave(@testfile)
advanceClock(1)
expect(fs.createReadStream).toHaveBeenCalledWith(download.targetPath)
expect(shell.showItemInFolder).not.toHaveBeenCalled()
onEndEventCallback()
advanceClock(1)
expect(shell.showItemInFolder).toHaveBeenCalledWith(download.targetPath)
it "should open an error if the download fails", ->
spyOn(FileDownloadStore, '_presentError')
spyOn(FileDownloadStore, '_runDownload').andCallFake =>
Promise.reject(@testdownload)
FileDownloadStore._fetchAndSave(@testfile)
advanceClock(1)
expect(FileDownloadStore._presentError).toHaveBeenCalled()
describe "_abortFetchFile", ->
beforeEach ->
@download =
ensureClosed: jasmine.createSpy('abort')
fileId: @testfile.id
FileDownloadStore._downloads[@testfile.id] = @download
it "should cancel the download for the provided file", ->
spyOn(fs, 'exists').andCallFake (path, callback) -> callback(true)
spyOn(fs, 'unlink')
FileDownloadStore._abortFetchFile(@testfile)
expect(fs.unlink).toHaveBeenCalled()
expect(@download.ensureClosed).toHaveBeenCalled()
it "should not try to delete the file if doesn't exist", ->
spyOn(fs, 'exists').andCallFake (path, callback) -> callback(false)
spyOn(fs, 'unlink')
FileDownloadStore._abortFetchFile(@testfile)
expect(fs.unlink).not.toHaveBeenCalled()
expect(@download.ensureClosed).toHaveBeenCalled()