diff --git a/spec/stores/draft-store-spec.coffee b/spec/stores/draft-store-spec.coffee index 202bd0876..38320c6b4 100644 --- a/spec/stores/draft-store-spec.coffee +++ b/spec/stores/draft-store-spec.coffee @@ -7,6 +7,7 @@ DraftStore, AccountStore, DatabaseStore, + FileUploadStore, SoundRegistry, SendDraftTask, ChangeMailTask, @@ -667,22 +668,25 @@ describe "DraftStore", -> describe "sending a draft", -> draftClientId = "local-123" beforeEach -> + @draft = new Message(clientId: draftClientId, threadId: "thread-123", replyToMessageId: "message-123") + @uploads = ['stub'] DraftStore._draftSessions = {} DraftStore._draftsSending = {} @forceCommit = false - msg = new Message(clientId: draftClientId, threadId: "thread-123", replyToMessageId: "message-123") proxy = prepare: -> Promise.resolve(proxy) teardown: -> - draft: -> msg + draft: => @draft changes: commit: ({force}={}) => @forceCommit = force Promise.resolve() + DraftStore._draftSessions[draftClientId] = proxy spyOn(DraftStore, "_doneWithSession").andCallThrough() spyOn(DraftStore, "trigger") spyOn(SoundRegistry, "playSound") + spyOn(FileUploadStore, "uploadsForMessage").andReturn(@uploads) spyOn(Actions, "queueTask") it "plays a sound immediately when sending draft", -> @@ -749,15 +753,7 @@ describe "DraftStore", -> runs -> expect(NylasEnv.close).not.toHaveBeenCalled() - it "forces a commit to happen before sending", -> - runs -> - DraftStore._onSendDraft(draftClientId) - waitsFor -> - DraftStore._doneWithSession.calls.length > 0 - runs -> - expect(@forceCommit).toBe true - - it "includes the threadId", -> + it "queues the correct SendDraftTask", -> runs -> DraftStore._onSendDraft(draftClientId) waitsFor -> @@ -766,18 +762,8 @@ describe "DraftStore", -> expect(Actions.queueTask).toHaveBeenCalled() task = Actions.queueTask.calls[0].args[0] expect(task instanceof SendDraftTask).toBe true - expect(task.threadId).toBe "thread-123" - - it "includes the replyToMessageId", -> - runs -> - DraftStore._onSendDraft(draftClientId) - waitsFor -> - DraftStore._doneWithSession.calls.length > 0 - runs -> - expect(Actions.queueTask).toHaveBeenCalled() - task = Actions.queueTask.calls[0].args[0] - expect(task instanceof SendDraftTask).toBe true - expect(task.replyToMessageId).toBe "message-123" + expect(task.draft).toBe @draft + expect(task.uploads).toBe @uploads it "queues a SendDraftTask", -> runs -> diff --git a/spec/stores/task-queue-spec.coffee b/spec/stores/task-queue-spec.coffee index 3b035a3f4..1b39ad47b 100644 --- a/spec/stores/task-queue-spec.coffee +++ b/spec/stores/task-queue-spec.coffee @@ -251,5 +251,4 @@ describe "TaskQueue", -> waitsForPromise => TaskQueue._processTask(@taskAA).then => expect(TaskQueue.dequeue).toHaveBeenCalledWith(@taskAA) - expect(spyAACallback).not.toHaveBeenCalled() expect(@taskAA.queueState.remoteError.message).toBe "Test Error" diff --git a/spec/tasks/send-draft-spec.coffee b/spec/tasks/send-draft-spec.coffee index 9c8fee8ae..cebe88ca0 100644 --- a/spec/tasks/send-draft-spec.coffee +++ b/spec/tasks/send-draft-spec.coffee @@ -4,6 +4,7 @@ _ = require 'underscore' DatabaseStore, DatabaseTransaction, Message, + Contact, Task, TaskQueue, SendDraftTask, @@ -28,65 +29,35 @@ describe "SendDraftTask", -> name: 'Dummy' email: 'dummy@nylas.com' - @draftB = new Message - version: '1' - clientId: 'localid-B' - serverId: '1233OTHERDRAFT' - accountId: 'A12ADE' - subject: 'New Draft' - draft: true - to: - name: 'Dummy' - email: 'dummy@nylas.com' - - @saveA = new SyncbackDraftTask('localid-A') - @saveB = new SyncbackDraftTask('localid-B') - @sendA = new SendDraftTask('localid-A') + @saveA = new SyncbackDraftTask(@draftA, []) + @sendA = new SendDraftTask(@draftA, []) expect(@sendA.isDependentTask(@saveA)).toBe(false) describe "performLocal", -> - it "throws an error if we we don't pass a draftClientId", -> + it "throws an error 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 "Attempt to call SendDraftTask.performLocal without @draftClientId." + expect(err.message).toBe "SendDraftTask - must be provided a draft." - it "finds the message and saves a backup copy of it", -> - draft = new Message - clientId: "local-123" - serverId: "server-123" - draft: true - - calledBody = "ERROR: The body wasn't included!" - spyOn(DatabaseStore, "findBy").andCallFake -> - then: -> throw new Error("You must include the body!") - include: (body) -> - calledBody = body - return Promise.resolve(draft) - - task = new SendDraftTask('local-123') - waitsForPromise => task.performLocal().then => - expect(task.backupDraft).toBeDefined() - expect(task.backupDraft.clientId).toBe "local-123" - expect(task.backupDraft.serverId).toBe "server-123" - expect(task.backupDraft).not.toBe draft # It's a clone - expect(task.replyToMessageId).not.toBeDefined() - expect(task.threadId).not.toBeDefined() - expect(calledBody).toBe Message.attributes.body + it "throws an error if we we don't pass uploads", -> + badTask = new SendDraftTask(new Message(), null) + badTask.performLocal() + .then -> + throw new Error("Shouldn't succeed") + .catch (err) -> + expect(err.message).toBe "SendDraftTask - must be provided an array of uploads." describe "performRemote", -> beforeEach -> - @accountId = "a123" - @draftClientId = "local-123" - @serverMessageId = '1233123AEDF1' - @response = version: 2 - id: @serverMessageId - account_id: @accountId + id: '1233123AEDF1' + account_id: TEST_ACCOUNT_ID + from: [new Contact(email: TEST_ACCOUNT_EMAIL)] subject: 'New Draft' body: 'hello world' to: @@ -105,128 +76,50 @@ describe "SendDraftTask", -> spyOn(Actions, "sendDraftSuccess") spyOn(NylasEnv, "emitError") - runFetchLatestDraftTests = -> - it "fetches the draft object from the DB", -> - waitsForPromise => @task._fetchLatestDraft().then (model) => - expect(model).toBe @draft - expect(@task.draftAccountId).toBe @draft.accountId - expect(@task.draftServerId).toBe @draft.serverId - expect(@task.draftVersion).toBe @draft.version - - it "throws a `NotFoundError` if the model is blank", -> - spyOn(@task, "_notifyUserOfError") - spyOn(@task, "_permanentError").andCallThrough() - @draftResolver = -> Promise.resolve(null) - waitsForPromise => @task.performRemote().then => - expect(DBt.persistModel.callCount).toBe 1 - expect(DBt.persistModel).toHaveBeenCalledWith(@backupDraft) - expect(@task._permanentError).toHaveBeenCalled() - - it "throws a `NotFoundError` if findBy fails", -> - spyOn(@task, "_notifyUserOfError") - spyOn(@task, "_permanentError").andCallThrough() - @draftResolver = -> Promise.reject(new Error("Test Problem")) - waitsForPromise => @task.performRemote().then => - expect(DBt.persistModel.callCount).toBe 1 - expect(DBt.persistModel).toHaveBeenCalledWith(@backupDraft) - expect(@task._permanentError).toHaveBeenCalled() - - # All of these are run in both the context of a saved draft and a new - # draft. - runMakeSendRequestTests = -> + sharedTests = => it "makes a send request with the correct data", -> - @task.draftAccountId = @accountId - waitsForPromise => @task._makeSendRequest(@draft).then => + waitsForPromise => @task._sendAndCreateMessage().then => expect(NylasAPI.makeRequest).toHaveBeenCalled() reqArgs = NylasAPI.makeRequest.calls[0].args[0] - expect(reqArgs.accountId).toBe @accountId + expect(reqArgs.accountId).toBe TEST_ACCOUNT_ID expect(reqArgs.body).toEqual @draft.toJSON() it "should pass returnsModel:false", -> - waitsForPromise => @task._makeSendRequest(@draft).then -> + waitsForPromise => @task._sendAndCreateMessage().then -> expect(NylasAPI.makeRequest.calls.length).toBe(1) options = NylasAPI.makeRequest.mostRecentCall.args[0] expect(options.returnsModel).toBe(false) it "should always send the draft body in the request body (joined attribute check)", -> waitsForPromise => - @task._makeSendRequest(@draft).then => + @task._sendAndCreateMessage().then => expect(NylasAPI.makeRequest.calls.length).toBe(1) options = NylasAPI.makeRequest.mostRecentCall.args[0] expect(options.body.body).toBe('hello world') it "should start an API request to /send", -> waitsForPromise => - @task._makeSendRequest(@draft).then => + @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') - it "retries the task if 'Invalid message public id'", -> - jasmine.unspy(NylasAPI, "makeRequest") - spyOn(NylasAPI, 'makeRequest').andCallFake (options) => - if options.body.reply_to_message_id - err = new APIError(body: "Invalid message public id") - Promise.reject(err) - else - options.success?(@response) - Promise.resolve(@response) - - @draft.replyToMessageId = "reply-123" - @draft.threadId = "thread-123" - waitsForPromise => @task._makeSendRequest(@draft).then => - expect(NylasAPI.makeRequest).toHaveBeenCalled() - expect(NylasAPI.makeRequest.callCount).toEqual 2 - req1 = NylasAPI.makeRequest.calls[0].args[0] - req2 = NylasAPI.makeRequest.calls[1].args[0] - expect(req1.body.reply_to_message_id).toBe "reply-123" - expect(req1.body.thread_id).toBe "thread-123" - - expect(req2.body.reply_to_message_id).toBe null - expect(req2.body.thread_id).toBe "thread-123" - - it "retries the task if 'Invalid message public id'", -> - jasmine.unspy(NylasAPI, "makeRequest") - spyOn(NylasAPI, 'makeRequest').andCallFake (options) => - if options.body.reply_to_message_id - err = new APIError(body: "Invalid thread") - Promise.reject(err) - else - options.success?(@response) - Promise.resolve(@response) - - @draft.replyToMessageId = "reply-123" - @draft.threadId = "thread-123" - waitsForPromise => @task._makeSendRequest(@draft).then => - expect(NylasAPI.makeRequest).toHaveBeenCalled() - expect(NylasAPI.makeRequest.callCount).toEqual 2 - req1 = NylasAPI.makeRequest.calls[0].args[0] - req2 = NylasAPI.makeRequest.calls[1].args[0] - expect(req1.body.reply_to_message_id).toBe "reply-123" - expect(req1.body.thread_id).toBe "thread-123" - - expect(req2.body.reply_to_message_id).toBe null - expect(req2.body.thread_id).toBe null - - runSaveNewMessageTests = -> it "should write the saved message to the database with the same client ID", -> waitsForPromise => - @task._saveNewMessage(@response).then => + @task._sendAndCreateMessage().then => expect(DBt.persistModel).toHaveBeenCalled() - expect(DBt.persistModel.mostRecentCall.args[0].clientId).toEqual(@draftClientId) - expect(DBt.persistModel.mostRecentCall.args[0].serverId).toEqual(@serverMessageId) + 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) - runNotifySuccess = -> it "should notify the draft was sent", -> waitsForPromise => @task.performRemote().then => args = Actions.sendDraftSuccess.calls[0].args[0] - expect(args.draftClientId).toBe @draftClientId + expect(args.draftClientId).toBe @draft.clientId it "get an object back on success", -> waitsForPromise => @task.performRemote().then => args = Actions.sendDraftSuccess.calls[0].args[0] - expect(args.newMessage.id).toBe @serverMessageId it "should play a sound", -> spyOn(NylasEnv.config, "get").andReturn true @@ -240,10 +133,9 @@ describe "SendDraftTask", -> expect(NylasEnv.config.get).toHaveBeenCalledWith("core.sending.sounds") expect(SoundRegistry.playSound).not.toHaveBeenCalled() - runIntegrativeWithErrors = -> describe "when there are errors", -> beforeEach -> - spyOn(@task, "_notifyUserOfError") + spyOn(Actions, 'draftSendingFailed') jasmine.unspy(NylasAPI, "makeRequest") it "notifies of a permanent error of misc error types", -> @@ -257,9 +149,53 @@ describe "SendDraftTask", -> @task.performRemote().then (status) => expect(status[0]).toBe Task.Status.Failed expect(status[1]).toBe thrownError - expect(@task._notifyUserOfError).toHaveBeenCalled() + expect(Actions.draftSendingFailed).toHaveBeenCalled() expect(NylasEnv.emitError).toHaveBeenCalled() + it "retries the task if 'Invalid message public id'", -> + spyOn(NylasAPI, 'makeRequest').andCallFake (options) => + if options.body.reply_to_message_id + err = new APIError(body: "Invalid message public id") + Promise.reject(err) + else + options.success?(@response) + Promise.resolve(@response) + + @draft.replyToMessageId = "reply-123" + @draft.threadId = "thread-123" + waitsForPromise => @task._sendAndCreateMessage(@draft).then => + expect(NylasAPI.makeRequest).toHaveBeenCalled() + expect(NylasAPI.makeRequest.callCount).toEqual 2 + req1 = NylasAPI.makeRequest.calls[0].args[0] + req2 = NylasAPI.makeRequest.calls[1].args[0] + expect(req1.body.reply_to_message_id).toBe "reply-123" + expect(req1.body.thread_id).toBe "thread-123" + + expect(req2.body.reply_to_message_id).toBe null + expect(req2.body.thread_id).toBe "thread-123" + + it "retries the task if 'Invalid message public id'", -> + spyOn(NylasAPI, 'makeRequest').andCallFake (options) => + if options.body.reply_to_message_id + err = new APIError(body: "Invalid thread") + Promise.reject(err) + else + options.success?(@response) + Promise.resolve(@response) + + @draft.replyToMessageId = "reply-123" + @draft.threadId = "thread-123" + waitsForPromise => @task._sendAndCreateMessage(@draft).then => + expect(NylasAPI.makeRequest).toHaveBeenCalled() + expect(NylasAPI.makeRequest.callCount).toEqual 2 + req1 = NylasAPI.makeRequest.calls[0].args[0] + req2 = NylasAPI.makeRequest.calls[1].args[0] + expect(req1.body.reply_to_message_id).toBe "reply-123" + expect(req1.body.thread_id).toBe "thread-123" + + expect(req2.body.reply_to_message_id).toBe null + expect(req2.body.thread_id).toBe null + it "notifies of a permanent error on 500 errors", -> thrownError = new APIError(statusCode: 500, body: "err") spyOn(NylasAPI, 'makeRequest').andCallFake (options) => @@ -267,8 +203,7 @@ describe "SendDraftTask", -> waitsForPromise => @task.performRemote().then (status) => expect(status[0]).toBe Task.Status.Failed expect(status[1]).toBe thrownError - expect(@task._notifyUserOfError).toHaveBeenCalled() - expect(NylasEnv.emitError).not.toHaveBeenCalled() + expect(Actions.draftSendingFailed).toHaveBeenCalled() it "notifies us and users of a permanent error on 400 errors", -> thrownError = new APIError(statusCode: 400, body: "err") @@ -277,8 +212,7 @@ describe "SendDraftTask", -> waitsForPromise => @task.performRemote().then (status) => expect(status[0]).toBe Task.Status.Failed expect(status[1]).toBe thrownError - expect(@task._notifyUserOfError).toHaveBeenCalled() - expect(NylasEnv.emitError).toHaveBeenCalled() + expect(Actions.draftSendingFailed).toHaveBeenCalled() it "notifies of a permanent error on timeouts", -> thrownError = new APIError(statusCode: NylasAPI.TimeoutErrorCode, body: "err") @@ -287,31 +221,19 @@ describe "SendDraftTask", -> waitsForPromise => @task.performRemote().then (status) => expect(status[0]).toBe Task.Status.Failed expect(status[1]).toBe thrownError - expect(@task._notifyUserOfError).toHaveBeenCalled() - expect(NylasEnv.emitError).not.toHaveBeenCalled() - - it "retries for other error types", -> - thrownError = new APIError(statusCode: 402, body: "err") - spyOn(NylasAPI, 'makeRequest').andCallFake (options) => - Promise.reject(thrownError) - waitsForPromise => @task.performRemote().then (status) => - expect(status).toBe Task.Status.Retry - expect(@task._notifyUserOfError).not.toHaveBeenCalled() - expect(NylasEnv.emitError).not.toHaveBeenCalled() + expect(Actions.draftSendingFailed).toHaveBeenCalled() describe "checking the promise chain halts on errors", -> beforeEach -> - spyOn(@task,"_makeSendRequest").andCallThrough() - spyOn(@task,"_saveNewMessage").andCallThrough() + spyOn(@task,"_sendAndCreateMessage").andCallThrough() spyOn(@task,"_deleteRemoteDraft").andCallThrough() - spyOn(@task,"_notifySuccess").andCallThrough() + spyOn(@task,"_onSuccess").andCallThrough() spyOn(@task,"_onError").andCallThrough() @expectBlockedChain = => - expect(@task._makeSendRequest).toHaveBeenCalled() - expect(@task._saveNewMessage).not.toHaveBeenCalled() + expect(@task._sendAndCreateMessage).toHaveBeenCalled() expect(@task._deleteRemoteDraft).not.toHaveBeenCalled() - expect(@task._notifySuccess).not.toHaveBeenCalled() + expect(@task._onSuccess).not.toHaveBeenCalled() expect(@task._onError).toHaveBeenCalled() it "halts on 500s", -> @@ -340,125 +262,108 @@ describe "SendDraftTask", -> options.success?(@response) Promise.resolve(@response) waitsForPromise => @task.performRemote().then (status) => - expect(@task._makeSendRequest).toHaveBeenCalled() - expect(@task._saveNewMessage).toHaveBeenCalled() + expect(@task._sendAndCreateMessage).toHaveBeenCalled() expect(@task._deleteRemoteDraft).toHaveBeenCalled() - expect(@task._notifySuccess).toHaveBeenCalled() + expect(@task._onSuccess).toHaveBeenCalled() expect(@task._onError).not.toHaveBeenCalled() - describe "with a new draft", -> beforeEach -> @draft = new Message version: 1 - clientId: @draftClientId - accountId: @accountId + clientId: 'client-id' + accountId: TEST_ACCOUNT_ID + from: [new Contact(email: TEST_ACCOUNT_EMAIL)] subject: 'New Draft' draft: true body: 'hello world' - @task = new SendDraftTask(@draftClientId) - @backupDraft = @draft.clone() - @task.backupDraft = @backupDraft # Since performLocal doesn't run - @draftResolver = -> Promise.resolve(@draft) + @uploads = [] + @task = new SendDraftTask(@draft, @uploads) @calledBody = "ERROR: The body wasn't included!" spyOn(DatabaseStore, "findBy").andCallFake => include: (body) => @calledBody = body - return @draftResolver() + Promise.resolve(@draft) + + sharedTests() it "can complete a full performRemote", -> waitsForPromise => @task.performRemote().then (status) -> expect(status).toBe Task.Status.Success - runFetchLatestDraftTests.call(@) - runMakeSendRequestTests.call(@) - runSaveNewMessageTests.call(@) - it "shouldn't attempt to delete a draft", -> waitsForPromise => - expect(@task.draftServerId).not.toBeDefined() @task._deleteRemoteDraft().then => expect(NylasAPI.makeRequest).not.toHaveBeenCalled() - runNotifySuccess.call(@) - runIntegrativeWithErrors.call(@) - it "should locally convert the draft to a message on send", -> - expect(@draft.clientId).toBe @draftClientId - expect(@draft.serverId).toBeUndefined() waitsForPromise => @task.performRemote().then => expect(DBt.persistModel).toHaveBeenCalled() model = DBt.persistModel.calls[0].args[0] - expect(model.clientId).toBe @draftClientId - expect(model.serverId).toBe @serverMessageId + expect(model.clientId).toBe @draft.clientId + expect(model.serverId).toBe @response.id expect(model.draft).toBe false - describe "with an existing persisted draft", -> beforeEach -> - @draftServerId = 'server-123' @draft = new Message version: 1 - clientId: @draftClientId - serverId: @draftServerId - accountId: @accountId + clientId: 'client-id' + serverId: 'server-123' + accountId: TEST_ACCOUNT_ID + from: [new Contact(email: TEST_ACCOUNT_EMAIL)] subject: 'New Draft' draft: true body: 'hello world' to: name: 'Dummy' email: 'dummy@nylas.com' - @task = new SendDraftTask(@draftClientId) - @backupDraft = @draft.clone() - @task.backupDraft = @backupDraft # Since performLocal doesn't run - @draftResolver = -> Promise.resolve(@draft) + @uploads = [] + @task = new SendDraftTask(@draft, @uploads) @calledBody = "ERROR: The body wasn't included!" spyOn(DatabaseStore, "findBy").andCallFake => then: -> throw new Error("You must include the body!") include: (body) => @calledBody = body - return @draftResolver() + Promise.resolve(@draft) + + sharedTests() it "can complete a full performRemote", -> waitsForPromise => + @task.performLocal() @task.performRemote().then (status) -> expect(status).toBe Task.Status.Success - runFetchLatestDraftTests.call(@) - runMakeSendRequestTests.call(@) - runSaveNewMessageTests.call(@) - it "should make a request to delete a draft", -> - waitsForPromise => @task._fetchLatestDraft().then(@task._deleteRemoteDraft).then => - expect(@task.draftServerId).toBe @draftServerId + @task.performLocal() + waitsForPromise => @task._deleteRemoteDraft().then => expect(NylasAPI.makeRequest).toHaveBeenCalled() expect(NylasAPI.makeRequest.callCount).toBe 1 req = NylasAPI.makeRequest.calls[0].args[0] - expect(req.path).toBe "/drafts/#{@draftServerId}" - expect(req.accountId).toBe @accountId + 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 failes", -> + it "should continue if the request fails", -> jasmine.unspy(NylasAPI, "makeRequest") - spyOn(console, "error") spyOn(NylasAPI, 'makeRequest').andCallFake (options) => - err = new APIError(body: "Boo", statusCode: 500) - Promise.reject(err) - waitsForPromise => @task._fetchLatestDraft().then(@task._deleteRemoteDraft).then => + Promise.reject(new APIError(body: "Boo", statusCode: 500)) + + @task.performLocal() + waitsForPromise => @task._deleteRemoteDraft().then => expect(NylasAPI.makeRequest).toHaveBeenCalled() expect(NylasAPI.makeRequest.callCount).toBe 1 - expect(console.error).toHaveBeenCalled() .catch => throw new Error("Shouldn't fail the promise") - runNotifySuccess.call(@) - runIntegrativeWithErrors.call(@) - it "should locally convert the existing draft to a message on send", -> - expect(@draft.clientId).toBe @draftClientId + expect(@draft.clientId).toBe @draft.clientId expect(@draft.serverId).toBe "server-123" + + @task.performLocal() waitsForPromise => @task.performRemote().then => expect(DBt.persistModel).toHaveBeenCalled() model = DBt.persistModel.calls[0].args[0] - expect(model.clientId).toBe @draftClientId - expect(model.serverId).toBe @serverMessageId + expect(model.clientId).toBe @draft.clientId + expect(model.serverId).toBe @response.id expect(model.draft).toBe false diff --git a/spec/tasks/syncback-draft-spec.coffee b/spec/tasks/syncback-draft-spec.coffee index e8e99337d..e6df2e405 100644 --- a/spec/tasks/syncback-draft-spec.coffee +++ b/spec/tasks/syncback-draft-spec.coffee @@ -100,28 +100,6 @@ describe "SyncbackDraftTask", -> expect(@taskB.queueState.isProcessing).toBe false expect(@taskB.runRemote).not.toHaveBeenCalled() - it "does not get dequeued if dependent tasks fail", -> - @taskA.queueState.localComplete = true - @taskB.queueState.localComplete = true - - spyOn(@taskA, "performRemote").andReturn Promise.resolve(Task.Status.Failed) - spyOn(@taskB, "performRemote").andReturn Promise.resolve(Task.Status.Success) - - spyOn(TaskQueue, "dequeue").andCallThrough() - spyOn(TaskQueue, "trigger") - - TaskQueue._queue = [@taskA, @taskB] - TaskQueue._processQueue() - advanceClock(100) - TaskQueue._processQueue() - advanceClock(100) - expect(@taskA.performRemote).toHaveBeenCalled() - expect(@taskB.performRemote).toHaveBeenCalled() - expect(TaskQueue.dequeue.calls.length).toBe 2 - - expect(@taskA.queueState.debugStatus).not.toBe Task.DebugStatus.DequeuedDependency - expect(@taskA.queueState.debugStatus).not.toBe Task.DebugStatus.DequeuedDependency - describe "performRemote", -> beforeEach -> spyOn(NylasAPI, 'makeRequest').andCallFake (opts) -> @@ -180,56 +158,25 @@ describe "SyncbackDraftTask", -> body: inboxError requestOptions: method: method ) - describe 'when PUT-ing', -> - beforeEach -> - @task = new SyncbackDraftTask("removeDraftId") - spyOn(@task, "getLatestLocalDraft").andCallFake -> Promise.resolve(remoteDraft()) - spyOn(@task, "detatchFromRemoteID").andCallFake -> Promise.resolve(remoteDraft()) - [400, 404, 409].forEach (code) -> - it "Retries on #{code} errors when we're PUT-ing", -> - stubAPI(code, "PUT") - waitsForPromise => - @task.performRemote().then (status) => - expect(@task.getLatestLocalDraft).toHaveBeenCalled() - expect(@task.getLatestLocalDraft.calls.length).toBe 2 - expect(@task.detatchFromRemoteID).toHaveBeenCalled() - expect(@task.detatchFromRemoteID.calls.length).toBe 1 - expect(status).toBe Task.Status.Retry + beforeEach -> + @task = new SyncbackDraftTask("removeDraftId") + spyOn(@task, "getLatestLocalDraft").andCallFake -> Promise.resolve(remoteDraft()) - [500, 0].forEach (code) -> - it "Fails on #{code} errors when we're PUT-ing", -> - stubAPI(code, "PUT") - waitsForPromise => - @task.performRemote().then ([status, err]) => - expect(status).toBe Task.Status.Failed - expect(@task.getLatestLocalDraft).toHaveBeenCalled() - expect(@task.getLatestLocalDraft.calls.length).toBe 1 - expect(@task.detatchFromRemoteID).not.toHaveBeenCalled() - expect(err.statusCode).toBe code - - describe 'when POST-ing', -> - beforeEach -> - @task = new SyncbackDraftTask("removeDraftId") - spyOn(@task, "getLatestLocalDraft").andCallFake -> Promise.resolve(localDraft()) - spyOn(@task, "detatchFromRemoteID").andCallFake -> Promise.resolve(localDraft()) - - [400, 404, 409, 500, 0].forEach (code) -> - it "Fails on #{code} errors when we're POST-ing", -> - stubAPI(code, "POST") - waitsForPromise => - @task.performRemote().then ([status, err]) => - expect(status).toBe Task.Status.Failed - expect(@task.getLatestLocalDraft).toHaveBeenCalled() - expect(@task.getLatestLocalDraft.calls.length).toBe 1 - expect(@task.detatchFromRemoteID).not.toHaveBeenCalled() - expect(err.statusCode).toBe code - - it "Fails on unknown errors", -> - spyOn(NylasAPI, "makeRequest").andCallFake -> Promise.reject(new APIError()) + [500, 400, 0].forEach (code) -> + it "Fails on #{code} errors when we're PUT-ing", -> + stubAPI(code, "PUT") waitsForPromise => @task.performRemote().then ([status, err]) => expect(status).toBe Task.Status.Failed expect(@task.getLatestLocalDraft).toHaveBeenCalled() expect(@task.getLatestLocalDraft.calls.length).toBe 1 - expect(@task.detatchFromRemoteID).not.toHaveBeenCalled() + expect(err.statusCode).toBe code + + it "Fails on unknown errors", -> + spyOn(NylasAPI, "makeRequest").andCallFake -> Promise.reject(new APIError()) + waitsForPromise => + @task.performRemote().then ([status, err]) => + expect(status).toBe Task.Status.Failed + expect(@task.getLatestLocalDraft).toHaveBeenCalled() + expect(@task.getLatestLocalDraft.calls.length).toBe 1 diff --git a/src/flux/tasks/send-draft.coffee b/src/flux/tasks/send-draft.coffee index 4979b6f8f..dca1fc6ee 100644 --- a/src/flux/tasks/send-draft.coffee +++ b/src/flux/tasks/send-draft.coffee @@ -52,6 +52,8 @@ class SendDraftTask extends Task return Promise.reject(new Error("SendDraftTask - must be provided a draft.")) unless @uploads and @uploads instanceof Array return Promise.reject(new Error("SendDraftTask - must be provided an array of uploads.")) + unless @draft.from[0] + return Promise.reject(new Error("SendDraftTask - you must populate `from` before sending.")) account = AccountStore.accountForEmail(@draft.from[0].email) unless account @@ -65,8 +67,10 @@ class SendDraftTask extends Task if account.id isnt @draft.accountId @draft.accountId = account.id - @draft.replyToMessageId = null - @draft.threadId = null + delete @draft.serverId + delete @draft.version + delete @draft.threadId + delete @draft.replyToMessageId Promise.resolve() @@ -174,19 +178,16 @@ class SendDraftTask extends Task return Promise.resolve(Task.Status.Success) _onError: (err) => - msg = "Your message could not be sent at this time. Please try again soon." + # OUTBOX COMING SOON! + + msg = "Your message could not be sent. Check your network connection and try again." if err instanceof APIError and err.statusCode is NylasAPI.TimeoutErrorCode msg = "We lost internet connection just as we were trying to send your message! Please wait a little bit to see if it went through. If not, check your internet connection and try sending again." - recoverableStatusCodes = [400, 404, 500, NylasAPI.TimeoutErrorCode] + Actions.draftSendingFailed + threadId: @draft.threadId + draftClientId: @draft.clientId, + errorMessage: msg + NylasEnv.emitError(err) - if err instanceof APIError and err.statusCode in recoverableStatusCodes - return Promise.resolve(Task.Status.Retry) - - else - Actions.draftSendingFailed - threadId: @draft.threadId - draftClientId: @draft.clientId, - errorMessage: msg - NylasEnv.emitError(err) - return Promise.resolve([Task.Status.Failed, err]) + return Promise.resolve([Task.Status.Failed, err]) diff --git a/src/flux/tasks/task.coffee b/src/flux/tasks/task.coffee index fba1376da..7488cc8c6 100644 --- a/src/flux/tasks/task.coffee +++ b/src/flux/tasks/task.coffee @@ -466,6 +466,7 @@ class Task # Returns `true` (should dequeue) or `false` (should not dequeue) shouldDequeueOtherTask: (other) -> false + onDependentTaskError: (other, error) -> ##### UNDO / REDO METHODS #####