mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-24 16:14:01 +08:00
test(send): Improved coverage of metadata logic, file uploads
Summary: Just new specs. Tested by manually breaking lots of things in SendDraft Test Plan: Run new tests Reviewers: juan, drew, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2649
This commit is contained in:
parent
66d1fe67e8
commit
0caa46bdab
2 changed files with 224 additions and 76 deletions
|
@ -1,4 +1,5 @@
|
|||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
{APIError,
|
||||
Actions,
|
||||
DatabaseStore,
|
||||
|
@ -18,7 +19,7 @@ describe "SendDraftTask", ->
|
|||
|
||||
describe "isDependentTask", ->
|
||||
it "is not dependent on any pending SyncbackDraftTasks", ->
|
||||
@draftA = new Message
|
||||
draftA = new Message
|
||||
version: '1'
|
||||
clientId: 'localid-A'
|
||||
serverId: '1233123AEDF1'
|
||||
|
@ -29,29 +30,41 @@ describe "SendDraftTask", ->
|
|||
name: 'Dummy'
|
||||
email: 'dummy@nylas.com'
|
||||
|
||||
@saveA = new SyncbackDraftTask(@draftA, [])
|
||||
@sendA = new SendDraftTask(@draftA, [])
|
||||
saveA = new SyncbackDraftTask(draftA, [])
|
||||
sendA = new SendDraftTask(draftA, [])
|
||||
|
||||
expect(@sendA.isDependentTask(@saveA)).toBe(false)
|
||||
expect(sendA.isDependentTask(saveA)).toBe(false)
|
||||
|
||||
describe "performLocal", ->
|
||||
it "throws an error if we we don't pass a draft", ->
|
||||
it "rejects if we we don't pass a draft", ->
|
||||
badTask = new SendDraftTask()
|
||||
badTask.performLocal()
|
||||
.then ->
|
||||
throw new Error("Shouldn't succeed")
|
||||
.catch (err) ->
|
||||
expect(err.message).toBe "SendDraftTask - must be provided a draft."
|
||||
badTask.performLocal().then ->
|
||||
throw new Error("Shouldn't succeed")
|
||||
.catch (err) ->
|
||||
expect(err.message).toBe "SendDraftTask - must be provided a draft."
|
||||
|
||||
it "throws an error if we we don't pass uploads", ->
|
||||
message = new Message()
|
||||
it "rejects if we we don't pass uploads", ->
|
||||
message = new Message(from: [new Contact(email: TEST_ACCOUNT_EMAIL)])
|
||||
message.uploads = null
|
||||
badTask = new SendDraftTask(message)
|
||||
badTask.performLocal()
|
||||
.then ->
|
||||
throw new Error("Shouldn't succeed")
|
||||
.catch (err) ->
|
||||
expect(err.message).toBe "SendDraftTask - must be provided an array of uploads."
|
||||
badTask.performLocal().then ->
|
||||
throw new Error("Shouldn't succeed")
|
||||
.catch (err) ->
|
||||
expect(err.message).toBe "SendDraftTask - must be provided an array of uploads."
|
||||
|
||||
it "rejects if no from address is specified", ->
|
||||
badTask = new SendDraftTask(new Message(from: [], uploads: []))
|
||||
badTask.performLocal().then ->
|
||||
throw new Error("Shouldn't succeed")
|
||||
.catch (err) ->
|
||||
expect(err.message).toBe "SendDraftTask - you must populate `from` before sending."
|
||||
|
||||
it "rejects if the from address does not map to any account", ->
|
||||
badTask = new SendDraftTask(new Message(from: [new Contact(email: 'not-configured@nylas.com')], uploads: null))
|
||||
badTask.performLocal().then ->
|
||||
throw new Error("Shouldn't succeed")
|
||||
.catch (err) ->
|
||||
expect(err.message).toBe "SendDraftTask - you can only send drafts from a configured account."
|
||||
|
||||
describe "performRemote", ->
|
||||
beforeEach ->
|
||||
|
@ -68,20 +81,35 @@ describe "SendDraftTask", ->
|
|||
|
||||
spyOn(NylasAPI, 'makeRequest').andCallFake (options) =>
|
||||
options.success?(@response)
|
||||
Promise.resolve(@response)
|
||||
return Promise.resolve(@response)
|
||||
|
||||
spyOn(DBt, 'unpersistModel').andReturn Promise.resolve()
|
||||
spyOn(DBt, 'persistModel').andReturn Promise.resolve()
|
||||
spyOn(SoundRegistry, "playSound")
|
||||
spyOn(Actions, "postNotification")
|
||||
spyOn(Actions, "sendDraftSuccess")
|
||||
spyOn(Actions, "attachmentUploaded")
|
||||
spyOn(fs, 'createReadStream').andReturn "stub"
|
||||
|
||||
# The tests below are invoked twice, once with a new @draft and one with a
|
||||
# persisted @draft.
|
||||
|
||||
sharedTests = =>
|
||||
it "should return Task.Status.Success", ->
|
||||
waitsForPromise =>
|
||||
@task.performLocal()
|
||||
@task.performRemote().then (status) ->
|
||||
expect(status).toBe Task.Status.Success
|
||||
|
||||
it "makes a send request with the correct data", ->
|
||||
waitsForPromise => @task._sendAndCreateMessage().then =>
|
||||
expect(NylasAPI.makeRequest).toHaveBeenCalled()
|
||||
reqArgs = NylasAPI.makeRequest.calls[0].args[0]
|
||||
expect(reqArgs.accountId).toBe TEST_ACCOUNT_ID
|
||||
expect(reqArgs.body).toEqual @draft.toJSON()
|
||||
expect(NylasAPI.makeRequest.callCount).toBe(1)
|
||||
options = NylasAPI.makeRequest.mostRecentCall.args[0]
|
||||
expect(options.path).toBe("/send")
|
||||
expect(options.method).toBe('POST')
|
||||
expect(options.accountId).toBe TEST_ACCOUNT_ID
|
||||
expect(options.body).toEqual @draft.toJSON()
|
||||
|
||||
it "should pass returnsModel:false", ->
|
||||
waitsForPromise => @task._sendAndCreateMessage().then ->
|
||||
|
@ -96,20 +124,28 @@ describe "SendDraftTask", ->
|
|||
options = NylasAPI.makeRequest.mostRecentCall.args[0]
|
||||
expect(options.body.body).toBe('hello world')
|
||||
|
||||
it "should start an API request to /send", -> waitsForPromise =>
|
||||
@task._sendAndCreateMessage().then =>
|
||||
expect(NylasAPI.makeRequest.calls.length).toBe(1)
|
||||
options = NylasAPI.makeRequest.mostRecentCall.args[0]
|
||||
expect(options.path).toBe("/send")
|
||||
expect(options.method).toBe('POST')
|
||||
describe "saving the sent message", ->
|
||||
it "should preserve the draft client id", ->
|
||||
waitsForPromise =>
|
||||
@task._sendAndCreateMessage().then =>
|
||||
expect(DBt.persistModel).toHaveBeenCalled()
|
||||
model = DBt.persistModel.mostRecentCall.args[0]
|
||||
expect(model.clientId).toEqual(@draft.clientId)
|
||||
expect(model.serverId).toEqual(@response.id)
|
||||
expect(model.draft).toEqual(false)
|
||||
|
||||
it "should write the saved message to the database with the same client ID", ->
|
||||
waitsForPromise =>
|
||||
@task._sendAndCreateMessage().then =>
|
||||
expect(DBt.persistModel).toHaveBeenCalled()
|
||||
expect(DBt.persistModel.mostRecentCall.args[0].clientId).toEqual(@draft.clientId)
|
||||
expect(DBt.persistModel.mostRecentCall.args[0].serverId).toEqual(@response.id)
|
||||
expect(DBt.persistModel.mostRecentCall.args[0].draft).toEqual(false)
|
||||
it "should preserve metadata, but not version numbers", ->
|
||||
waitsForPromise =>
|
||||
@task._sendAndCreateMessage().then =>
|
||||
expect(DBt.persistModel).toHaveBeenCalled()
|
||||
model = DBt.persistModel.mostRecentCall.args[0]
|
||||
|
||||
expect(model.pluginMetadata.length).toEqual(@draft.pluginMetadata.length)
|
||||
|
||||
for {pluginId, value, version} in @draft.pluginMetadata
|
||||
updated = model.metadataObjectForPluginId(pluginId)
|
||||
expect(updated.value).toEqual(value)
|
||||
expect(updated.version).toEqual(0)
|
||||
|
||||
it "should notify the draft was sent", ->
|
||||
waitsForPromise =>
|
||||
|
@ -118,6 +154,17 @@ describe "SendDraftTask", ->
|
|||
expect(args.message instanceof Message).toBe(true)
|
||||
expect(args.messageClientId).toBe(@draft.clientId)
|
||||
|
||||
it "should queue tasks to sync back the metadata on the new message", ->
|
||||
waitsForPromise =>
|
||||
spyOn(Actions, 'queueTask')
|
||||
@task.performRemote().then =>
|
||||
metadataTasks = Actions.queueTask.calls.map (call) -> call.args[0]
|
||||
expect(metadataTasks.length).toEqual(@draft.pluginMetadata.length)
|
||||
for pluginMetadatum, idx in @draft.pluginMetadata
|
||||
expect(metadataTasks[idx].clientId).toEqual(@draft.clientId)
|
||||
expect(metadataTasks[idx].modelClassName).toEqual('Message')
|
||||
expect(metadataTasks[idx].pluginId).toEqual(pluginMetadatum.pluginId)
|
||||
|
||||
it "should play a sound", ->
|
||||
spyOn(NylasEnv.config, "get").andReturn true
|
||||
waitsForPromise => @task.performRemote().then ->
|
||||
|
@ -338,6 +385,11 @@ describe "SendDraftTask", ->
|
|||
draft: true
|
||||
body: 'hello world'
|
||||
uploads: []
|
||||
|
||||
@draft.applyPluginMetadata('pluginIdA', {tracked: true})
|
||||
@draft.applyPluginMetadata('pluginIdB', {a: true, b: 2})
|
||||
@draft.metadataObjectForPluginId('pluginIdA').version = 2
|
||||
|
||||
@task = new SendDraftTask(@draft)
|
||||
@calledBody = "ERROR: The body wasn't included!"
|
||||
spyOn(DatabaseStore, "findBy").andCallFake =>
|
||||
|
@ -345,16 +397,6 @@ describe "SendDraftTask", ->
|
|||
|
||||
sharedTests()
|
||||
|
||||
it "can complete a full performRemote", ->
|
||||
waitsForPromise =>
|
||||
@task.performRemote().then (status) ->
|
||||
expect(status).toBe Task.Status.Success
|
||||
|
||||
it "shouldn't attempt to delete a draft", ->
|
||||
waitsForPromise =>
|
||||
@task._deleteRemoteDraft(@draft).then =>
|
||||
expect(NylasAPI.makeRequest).not.toHaveBeenCalled()
|
||||
|
||||
it "should locally convert the draft to a message on send", ->
|
||||
waitsForPromise =>
|
||||
@task.performRemote().then =>
|
||||
|
@ -364,6 +406,13 @@ describe "SendDraftTask", ->
|
|||
expect(model.serverId).toBe @response.id
|
||||
expect(model.draft).toBe false
|
||||
|
||||
describe "deleteRemoteDraft", ->
|
||||
it "should not make an API request", ->
|
||||
waitsForPromise =>
|
||||
@task._deleteRemoteDraft(@draft).then =>
|
||||
expect(NylasAPI.makeRequest).not.toHaveBeenCalled()
|
||||
|
||||
|
||||
describe "with an existing persisted draft", ->
|
||||
beforeEach ->
|
||||
@draft = new Message
|
||||
|
@ -379,6 +428,11 @@ describe "SendDraftTask", ->
|
|||
name: 'Dummy'
|
||||
email: 'dummy@nylas.com'
|
||||
uploads: []
|
||||
|
||||
@draft.applyPluginMetadata('pluginIdA', {tracked: true})
|
||||
@draft.applyPluginMetadata('pluginIdB', {a: true, b: 2})
|
||||
@draft.metadataObjectForPluginId('pluginIdA').version = 2
|
||||
|
||||
@task = new SendDraftTask(@draft)
|
||||
@calledBody = "ERROR: The body wasn't included!"
|
||||
spyOn(DatabaseStore, "findBy").andCallFake =>
|
||||
|
@ -386,37 +440,6 @@ describe "SendDraftTask", ->
|
|||
|
||||
sharedTests()
|
||||
|
||||
it "can complete a full performRemote", -> waitsForPromise =>
|
||||
@task.performLocal()
|
||||
@task.performRemote().then (status) ->
|
||||
expect(status).toBe Task.Status.Success
|
||||
|
||||
it "should make a request to delete a draft", ->
|
||||
@task.performLocal()
|
||||
waitsForPromise =>
|
||||
@task._deleteRemoteDraft(@draft).then =>
|
||||
expect(NylasAPI.makeRequest).toHaveBeenCalled()
|
||||
expect(NylasAPI.makeRequest.callCount).toBe 1
|
||||
req = NylasAPI.makeRequest.calls[0].args[0]
|
||||
expect(req.path).toBe "/drafts/#{@draft.serverId}"
|
||||
expect(req.accountId).toBe TEST_ACCOUNT_ID
|
||||
expect(req.method).toBe "DELETE"
|
||||
expect(req.returnsModel).toBe false
|
||||
|
||||
it "should continue if the request fails", ->
|
||||
jasmine.unspy(NylasAPI, "makeRequest")
|
||||
spyOn(NylasAPI, 'makeRequest').andCallFake (options) =>
|
||||
Promise.reject(new APIError(body: "Boo", statusCode: 500))
|
||||
|
||||
@task.performLocal()
|
||||
waitsForPromise =>
|
||||
@task._deleteRemoteDraft(@draft)
|
||||
.then =>
|
||||
expect(NylasAPI.makeRequest).toHaveBeenCalled()
|
||||
expect(NylasAPI.makeRequest.callCount).toBe 1
|
||||
.catch =>
|
||||
throw new Error("Shouldn't fail the promise")
|
||||
|
||||
it "should locally convert the existing draft to a message on send", ->
|
||||
expect(@draft.clientId).toBe @draft.clientId
|
||||
expect(@draft.serverId).toBe "server-123"
|
||||
|
@ -428,3 +451,129 @@ describe "SendDraftTask", ->
|
|||
expect(model.clientId).toBe @draft.clientId
|
||||
expect(model.serverId).toBe @response.id
|
||||
expect(model.draft).toBe false
|
||||
|
||||
describe "deleteRemoteDraft", ->
|
||||
it "should make an API request to delete the draft", ->
|
||||
@task.performLocal()
|
||||
waitsForPromise =>
|
||||
@task._deleteRemoteDraft(@draft).then =>
|
||||
expect(NylasAPI.makeRequest).toHaveBeenCalled()
|
||||
expect(NylasAPI.makeRequest.callCount).toBe 1
|
||||
req = NylasAPI.makeRequest.calls[0].args[0]
|
||||
expect(req.path).toBe "/drafts/#{@draft.serverId}"
|
||||
expect(req.accountId).toBe TEST_ACCOUNT_ID
|
||||
expect(req.method).toBe "DELETE"
|
||||
expect(req.returnsModel).toBe false
|
||||
|
||||
it "should continue if the request fails", ->
|
||||
jasmine.unspy(NylasAPI, "makeRequest")
|
||||
spyOn(NylasAPI, 'makeRequest').andCallFake (options) =>
|
||||
Promise.reject(new APIError(body: "Boo", statusCode: 500))
|
||||
|
||||
@task.performLocal()
|
||||
waitsForPromise =>
|
||||
@task._deleteRemoteDraft(@draft)
|
||||
.then =>
|
||||
expect(NylasAPI.makeRequest).toHaveBeenCalled()
|
||||
expect(NylasAPI.makeRequest.callCount).toBe 1
|
||||
.catch =>
|
||||
throw new Error("Shouldn't fail the promise")
|
||||
|
||||
describe "with uploads", ->
|
||||
beforeEach ->
|
||||
@uploads = [
|
||||
{targetPath: '/test-file-1.png', size: 100},
|
||||
{targetPath: '/test-file-2.png', size: 100}
|
||||
]
|
||||
@draft = new Message
|
||||
version: 1
|
||||
clientId: 'client-id'
|
||||
accountId: TEST_ACCOUNT_ID
|
||||
from: [new Contact(email: TEST_ACCOUNT_EMAIL)]
|
||||
subject: 'New Draft'
|
||||
draft: true
|
||||
body: 'hello world'
|
||||
uploads: [].concat(@uploads)
|
||||
|
||||
@task = new SendDraftTask(@draft)
|
||||
jasmine.unspy(NylasAPI, 'makeRequest')
|
||||
|
||||
@resolves = []
|
||||
@resolveAll = =>
|
||||
resolve() for resolve in @resolves
|
||||
@resolves = []
|
||||
advanceClock()
|
||||
|
||||
spyOn(NylasAPI, 'makeRequest').andCallFake (options) =>
|
||||
response = @response
|
||||
|
||||
if options.path is '/files'
|
||||
response = JSON.stringify([{
|
||||
id: '1234'
|
||||
account_id: TEST_ACCOUNT_ID
|
||||
filename: options.formData.file.options.filename
|
||||
}])
|
||||
|
||||
new Promise (resolve, reject) =>
|
||||
@resolves.push =>
|
||||
options.success?(response)
|
||||
resolve(response)
|
||||
|
||||
spyOn(DatabaseStore, 'findBy').andCallFake =>
|
||||
Promise.resolve(@draft)
|
||||
|
||||
it "should begin file uploads and not hit /send until they complete", ->
|
||||
@task.performRemote()
|
||||
advanceClock()
|
||||
|
||||
# uploads should be queued, but not the send
|
||||
expect(NylasAPI.makeRequest.callCount).toEqual(2)
|
||||
expect(NylasAPI.makeRequest.calls[0].args[0].formData).toEqual({ file : { value : 'stub', options : { filename : 'test-file-1.png' } } })
|
||||
expect(NylasAPI.makeRequest.calls[1].args[0].formData).toEqual({ file : { value : 'stub', options : { filename : 'test-file-2.png' } } })
|
||||
|
||||
# finish all uploads
|
||||
@resolveAll()
|
||||
|
||||
# send should now be queued
|
||||
expect(NylasAPI.makeRequest.callCount).toEqual(3)
|
||||
expect(NylasAPI.makeRequest.calls[2].args[0].path).toEqual('/send')
|
||||
|
||||
it "should convert the uploads to files", ->
|
||||
@task.performRemote()
|
||||
advanceClock()
|
||||
expect(@task.draft.files.length).toEqual(0)
|
||||
expect(@task.draft.uploads.length).toEqual(2)
|
||||
@resolves[0]()
|
||||
advanceClock()
|
||||
expect(@task.draft.files.length).toEqual(1)
|
||||
expect(@task.draft.uploads.length).toEqual(1)
|
||||
|
||||
{filename, accountId, id} = @task.draft.files[0]
|
||||
expect({filename, accountId, id}).toEqual({
|
||||
filename: 'test-file-1.png',
|
||||
accountId: TEST_ACCOUNT_ID,
|
||||
id: '1234'
|
||||
})
|
||||
|
||||
describe "cancel, during attachment upload", ->
|
||||
it "should make the task resolve early, before making the /send call", ->
|
||||
exitStatus = null
|
||||
@task.performRemote().then (status) => exitStatus = status
|
||||
advanceClock()
|
||||
@task.cancel()
|
||||
NylasAPI.makeRequest.reset()
|
||||
@resolveAll()
|
||||
advanceClock()
|
||||
expect(NylasAPI.makeRequest).not.toHaveBeenCalled()
|
||||
expect(exitStatus).toEqual(Task.Status.Continue)
|
||||
|
||||
describe "after the message sends", ->
|
||||
it "should notify the attachments were uploaded (so they can be deleted)", ->
|
||||
@task.performRemote()
|
||||
advanceClock()
|
||||
@resolveAll() # uploads
|
||||
@resolveAll() # send
|
||||
expect(Actions.attachmentUploaded).toHaveBeenCalled()
|
||||
expect(Actions.attachmentUploaded.callCount).toEqual(@uploads.length)
|
||||
for upload, idx in @uploads
|
||||
expect(Actions.attachmentUploaded.calls[idx].args[0]).toBe(upload)
|
||||
|
|
|
@ -188,7 +188,6 @@ class SendDraftTask extends Task
|
|||
Promise.resolve()
|
||||
|
||||
_onSuccess: =>
|
||||
|
||||
# Delete attachments from the uploads folder
|
||||
for upload in @uploaded
|
||||
Actions.attachmentUploaded(upload)
|
||||
|
|
Loading…
Reference in a new issue