Mailspring/spec-inbox/stores/task-queue-spec.coffee
Ben Gotow e1fc34a562 fix(drafts): Draft syncing completely disabled to reduce code complexity
Summary:
fix(streaming): Reconnect every 30 seconds, always

Never accept drafts via any API source

fix(attachments): Fix for changes to open API

Get rid of shouldAbort, just let tasks decide what to do in cleanup

Never let SaveDraftTask run while another SaveDraftTask for same draft is running

Never used IPC

Ignore destroy draft 404

Moving draft store proxy to draft store level

Only block on older saves

Replace SaveDraftTask with SyncbackDraftTask, do saving directly from proxy

Never sync back ;-)

Fix specs

Alter SendDraftTask so that it can send an unsaved draft

Test Plan: Run tests

Reviewers: evan

Reviewed By: evan

Differential Revision: https://review.inboxapp.com/D1245
2015-03-02 11:23:35 -08:00

373 lines
13 KiB
CoffeeScript

Actions = require '../../src/flux/actions'
TaskQueue = require '../../src/flux/stores/task-queue'
Task = require '../../src/flux/tasks/task'
{isTempId} = require '../../src/flux/models/utils'
{APIError,
OfflineError,
TimeoutError} = require '../../src/flux/errors'
class TaskSubclassA extends Task
constructor: (val) -> @aProp = val # forgot to call super
class TaskSubclassB extends Task
constructor: (val) -> @bProp = val; super
describe "TaskQueue", ->
makeUnstartedTask = (task) ->
TaskQueue._initializeTask(task)
return task
makeLocalStarted = (task) ->
TaskQueue._initializeTask(task)
task.queueState.isProcessing = true
return task
makeLocalFailed = (task) ->
TaskQueue._initializeTask(task)
task.queueState.performedLocal = Date.now()
return task
makeRemoteStarted = (task) ->
TaskQueue._initializeTask(task)
task.queueState.isProcessing = true
task.queueState.remoteAttempts = 1
task.queueState.performedLocal = Date.now()
return task
makeRemoteSuccess = (task) ->
TaskQueue._initializeTask(task)
task.queueState.remoteAttempts = 1
task.queueState.performedLocal = Date.now()
task.queueState.performedRemote = Date.now()
return task
makeRemoteFailed = (task) ->
TaskQueue._initializeTask(task)
task.queueState.remoteAttempts = 1
task.queueState.performedLocal = Date.now()
return task
beforeEach ->
TaskQueue._onlineStatus = true
@task = new Task()
@unstartedTask = makeUnstartedTask(new Task())
@localStarted = makeLocalStarted(new Task())
@localFailed = makeLocalFailed(new Task())
@remoteStarted = makeRemoteStarted(new Task())
@remoteSuccess = makeRemoteSuccess(new Task())
@remoteFailed = makeRemoteFailed(new Task())
unstartedTask = (task) ->
taks.queueState.shouldRetry = false
taks.queueState.isProcessing = false
taks.queueState.remoteAttempts = 0
taks.queueState.perfomredLocal = false
taks.queueState.performedRemote = false
taks.queueState.notifiedOffline = false
startedTask = (task) ->
taks.queueState.shouldRetry = false
taks.queueState.isProcessing = true
taks.queueState.remoteAttempts = 0
taks.queueState.perfomredLocal = false
taks.queueState.performedRemote = false
taks.queueState.notifiedOffline = false
localTask = (task) ->
taks.queueState.shouldRetry = false
taks.queueState.isProcessing = true
taks.queueState.remoteAttempts = 0
taks.queueState.perfomredLocal = false
taks.queueState.performedRemote = false
taks.queueState.notifiedOffline = false
afterEach ->
TaskQueue._queue = []
TaskQueue._completed = []
describe "enqueue", ->
it "makes sure you've queued a real task", ->
expect( -> TaskQueue.enqueue("asamw")).toThrow()
it "adds it to the queue", ->
TaskQueue.enqueue(@task)
expect(TaskQueue._queue.length).toBe 1
it "notifies the queue should be processed", ->
spyOn(TaskQueue, "_processTask")
spyOn(TaskQueue, "_processQueue").andCallThrough()
TaskQueue.enqueue(@task)
expect(TaskQueue._processQueue).toHaveBeenCalled()
expect(TaskQueue._processTask).toHaveBeenCalledWith(@task)
expect(TaskQueue._processTask.calls.length).toBe 1
it "ensures all tasks have an id", ->
TaskQueue.enqueue(new TaskSubclassA())
TaskQueue.enqueue(new TaskSubclassB())
expect(isTempId(TaskQueue._queue[0].id)).toBe true
expect(isTempId(TaskQueue._queue[1].id)).toBe true
it "dequeues Obsolete tasks", ->
class KillsTaskA extends Task
constructor: ->
shouldDequeueOtherTask: (other) -> other instanceof TaskSubclassA
taskToDie = makeRemoteFailed(new TaskSubclassA())
spyOn(TaskQueue, "dequeue").andCallThrough()
TaskQueue._queue = [taskToDie, @remoteFailed]
TaskQueue.enqueue(new KillsTaskA())
expect(TaskQueue._queue.length).toBe 2
expect(TaskQueue.dequeue).toHaveBeenCalledWith(taskToDie, silent: true)
expect(TaskQueue.dequeue.calls.length).toBe 1
describe "dequeue", ->
beforeEach ->
TaskQueue._queue = [@unstartedTask,
@localStarted,
@remoteStarted,
@remoteFailed]
it "grabs the task by object", ->
found = TaskQueue._parseArgs(@remoteStarted)
expect(found).toBe @remoteStarted
it "grabs the task by id", ->
found = TaskQueue._parseArgs(@remoteStarted.id)
expect(found).toBe @remoteStarted
it "throws an error if the task isn't found", ->
expect( -> TaskQueue.dequeue("bad")).toThrow()
it "calls cleanup on dequeued tasks", ->
spyOn(@remoteStarted, "cleanup")
TaskQueue.dequeue(@remoteStarted, silent: true)
expect(@remoteStarted.cleanup).toHaveBeenCalled()
it "moves it from the queue", ->
TaskQueue.dequeue(@remoteStarted, silent: true)
expect(TaskQueue._queue.length).toBe 3
expect(TaskQueue._completed.length).toBe 1
it "marks it as no longer processing", ->
TaskQueue.dequeue(@remoteStarted, silent: true)
expect(@remoteStarted.queueState.isProcessing).toBe false
it "notifies the queue has been updated", ->
spyOn(TaskQueue, "_processQueue")
TaskQueue.dequeue(@remoteStarted)
expect(TaskQueue._processQueue).toHaveBeenCalled()
expect(TaskQueue._processQueue.calls.length).toBe 1
describe "process Task", ->
it "doesn't process processing tasks", ->
spyOn(@remoteStarted, "performLocal")
spyOn(@remoteStarted, "performRemote")
TaskQueue._processTask(@remoteStarted)
expect(@remoteStarted.performLocal).not.toHaveBeenCalled()
expect(@remoteStarted.performRemote).not.toHaveBeenCalled()
it "doesn't process blocked tasks", ->
class BlockedByTaskA extends Task
constructor: ->
shouldWaitForTask: (other) -> other instanceof TaskSubclassA
blockedByTask = new BlockedByTaskA()
spyOn(blockedByTask, "performLocal")
spyOn(blockedByTask, "performRemote")
blockingTask = makeRemoteFailed(new TaskSubclassA())
TaskQueue._queue = [blockingTask, @remoteFailed]
TaskQueue.enqueue(blockedByTask)
expect(TaskQueue._queue.length).toBe 3
expect(blockedByTask.performLocal).not.toHaveBeenCalled()
expect(blockedByTask.performRemote).not.toHaveBeenCalled()
it "doesn't block itself", ->
class BlockingTask extends Task
constructor: ->
shouldWaitForTask: (other) -> other instanceof BlockingTask
blockedByTask = new BlockingTask()
spyOn(blockedByTask, "performLocal")
spyOn(blockedByTask, "performRemote")
blockingTask = makeRemoteFailed(new BlockingTask())
TaskQueue._queue = [blockingTask, @remoteFailed]
TaskQueue.enqueue(blockedByTask)
expect(TaskQueue._queue.length).toBe 3
expect(blockedByTask.performLocal).not.toHaveBeenCalled()
expect(blockedByTask.performRemote).not.toHaveBeenCalled()
it "sets the processing bit", ->
spyOn(@unstartedTask, "performLocal").andCallFake -> Promise.resolve()
TaskQueue._processTask(@unstartedTask)
expect(@unstartedTask.queueState.isProcessing).toBe true
it "performs local if it's a fresh task", ->
spyOn(@unstartedTask, "performLocal").andCallFake -> Promise.resolve()
TaskQueue._processTask(@unstartedTask)
expect(@unstartedTask.performLocal).toHaveBeenCalled()
describe "performLocal", ->
it "on success it marks it as complete with the timestamp", ->
spyOn(@unstartedTask, "performLocal").andCallFake -> Promise.resolve()
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.performedLocal isnt false
runs ->
expect(@unstartedTask.queueState.performedLocal).toBeGreaterThan 0
it "throws an error if it fails", ->
spyOn(@unstartedTask, "performLocal").andCallFake -> Promise.reject("boo")
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.isProcessing == false
runs ->
expect(@unstartedTask.queueState.localError).toBe "boo"
expect(@unstartedTask.performLocal).toHaveBeenCalled()
expect(@unstartedTask.performRemote).not.toHaveBeenCalled()
it "dequeues the task if it fails locally", ->
spyOn(@unstartedTask, "performLocal").andCallFake -> Promise.reject("boo")
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.isProcessing == false
runs ->
expect(TaskQueue._queue.length).toBe 0
expect(TaskQueue._completed.length).toBe 1
describe "performRemote", ->
beforeEach ->
spyOn(@unstartedTask, "performLocal").andCallFake -> Promise.resolve()
it "performs remote properly", ->
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.performedRemote isnt false
runs ->
expect(@unstartedTask.performLocal).toHaveBeenCalled()
expect(@unstartedTask.performRemote).toHaveBeenCalled()
it "dequeues on success", ->
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.isProcessing is false and
@unstartedTask.queueState.performedRemote > 0
runs ->
expect(TaskQueue._queue.length).toBe 0
expect(TaskQueue._completed.length).toBe 1
it "notifies we're offline the first time", ->
spyOn(TaskQueue, "_isOnline").andReturn false
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
spyOn(@unstartedTask, "onError")
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.notifiedOffline == true
runs ->
expect(@unstartedTask.performLocal).toHaveBeenCalled()
expect(@unstartedTask.performRemote).not.toHaveBeenCalled()
expect(@unstartedTask.onError).toHaveBeenCalled()
expect(@unstartedTask.queueState.isProcessing).toBe false
expect(@unstartedTask.onError.calls[0].args[0] instanceof OfflineError).toBe true
it "doesn't notify we're offline the second+ time", ->
spyOn(TaskQueue, "_isOnline").andReturn false
spyOn(@remoteFailed, "performLocal").andCallFake -> Promise.resolve()
spyOn(@remoteFailed, "performRemote").andCallFake -> Promise.resolve()
spyOn(@remoteFailed, "onError")
@remoteFailed.queueState.notifiedOffline = true
TaskQueue._queue = [@remoteFailed]
runs ->
TaskQueue._processQueue()
waitsFor =>
@remoteFailed.queueState.isProcessing is false
runs ->
expect(@remoteFailed.performLocal).not.toHaveBeenCalled()
expect(@remoteFailed.performRemote).not.toHaveBeenCalled()
expect(@remoteFailed.onError).not.toHaveBeenCalled()
it "marks performedRemote on success", ->
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.performedRemote isnt false
runs ->
expect(@unstartedTask.queueState.performedRemote).toBeGreaterThan 0
it "on failure it notifies of the error", ->
err = new APIError
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.reject(err)
spyOn(@unstartedTask, "onError")
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.isProcessing is false
runs ->
expect(@unstartedTask.performLocal).toHaveBeenCalled()
expect(@unstartedTask.performRemote).toHaveBeenCalled()
expect(@unstartedTask.onError).toHaveBeenCalledWith(err)
it "dequeues on failure", ->
err = new APIError
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.reject(err)
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.isProcessing is false
runs ->
expect(TaskQueue._queue.length).toBe 0
expect(TaskQueue._completed.length).toBe 1
it "on failure it sets the appropriate bits", ->
err = new APIError
spyOn(@unstartedTask, "performRemote").andCallFake -> Promise.reject(err)
spyOn(@unstartedTask, "onError")
runs ->
TaskQueue.enqueue(@unstartedTask)
waitsFor =>
@unstartedTask.queueState.isProcessing is false
runs ->
expect(@unstartedTask.queueState.notifiedOffline).toBe false
expect(@unstartedTask.queueState.remoteError).toBe err
describe "under stress", ->
beforeEach ->
TaskQueue._queue = [@unstartedTask,
@remoteFailed]
it "when all tasks pass it processes all items", ->
for task in TaskQueue._queue
spyOn(task, "performLocal").andCallFake -> Promise.resolve()
spyOn(task, "performRemote").andCallFake -> Promise.resolve()
runs ->
TaskQueue.enqueue(new Task)
waitsFor ->
TaskQueue._queue.length is 0
runs ->
expect(TaskQueue._completed.length).toBe 3