Mailspring/spec/tasks/destroy-category-task-spec.coffee

135 lines
4.4 KiB
CoffeeScript
Raw Normal View History

feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
{DestroyCategoryTask,
NylasAPI,
Task,
Category,
AccountStore,
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
APIError,
Category,
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
DatabaseStore,
DatabaseTransaction} = require "nylas-exports"
describe "DestroyCategoryTask", ->
pathOf = (fn) ->
fn.calls[0].args[0].path
methodOf = (fn) ->
fn.calls[0].args[0].method
accountIdOf = (fn) ->
fn.calls[0].args[0].accountId
nameOf = (fn) ->
fn.calls[0].args[0].body.display_name
makeAccount = ({usesFolders, usesLabels} = {}) ->
spyOn(AccountStore, "accountForId").andReturn {
usesFolders: -> usesFolders
usesLabels: -> usesLabels
}
makeTask = ->
category = new Category
displayName: "important emails"
accountId: "account 123"
serverId: "server-444"
new DestroyCategoryTask
category: category
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
beforeEach ->
spyOn(DatabaseTransaction.prototype, 'unpersistModel').andCallFake -> Promise.resolve()
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
spyOn(DatabaseTransaction.prototype, 'persistModel').andCallFake -> Promise.resolve()
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
describe "performLocal", ->
it "sets an `isDeleted` flag and persists the category", ->
task = makeTask()
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
runs =>
task.performLocal()
waitsFor =>
DatabaseTransaction.prototype.unpersistModel.callCount > 0
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
runs =>
model = DatabaseTransaction.prototype.unpersistModel.calls[0].args[0]
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
expect(model.serverId).toEqual "server-444"
describe "performRemote", ->
it "throws error when no category present", ->
waitsForPromise ->
task = makeTask()
task.category = null
task.performRemote()
.then ->
throw new Error('The promise should reject')
.catch Error, (err) ->
expect(err).toBeDefined()
it "throws error when category does not have a serverId", ->
waitsForPromise ->
task = makeTask()
task.category.serverId = undefined
task.performRemote()
.then ->
throw new Error('The promise should reject')
.catch Error, (err) ->
expect(err).toBeDefined()
describe "when request succeeds", ->
beforeEach ->
spyOn(NylasAPI, "makeRequest").andCallFake -> Promise.resolve("null")
spyOn(NylasAPI, "incrementRemoteChangeLock")
it "blocks other remote changes to that category", ->
makeAccount()
task = makeTask()
task.performRemote()
expect(NylasAPI.incrementRemoteChangeLock).toHaveBeenCalled()
it "sends API req to /labels if user uses labels", ->
makeAccount(usesLabels: true)
task = makeTask()
task.performRemote()
expect(pathOf(NylasAPI.makeRequest)).toBe "/labels/server-444"
it "sends API req to /folders if user uses folders", ->
makeAccount(usesFolders: true)
task = makeTask()
task.performRemote()
expect(pathOf(NylasAPI.makeRequest)).toBe "/folders/server-444"
it "sends DELETE request", ->
makeAccount()
task = makeTask()
task.performRemote()
expect(methodOf(NylasAPI.makeRequest)).toBe "DELETE"
it "sends the account id", ->
makeAccount()
task = makeTask()
task.performRemote()
expect(accountIdOf(NylasAPI.makeRequest)).toBe "account 123"
describe "when request fails", ->
beforeEach ->
makeAccount()
spyOn(NylasAPI, 'decrementRemoteChangeLock')
feat(error): improve error reporting. Now `NylasEnv.reportError` Summary: The goal is to let us see what plugins are throwing errors on Sentry. We are using a Sentry `tag` to identify and group plugins and their errors. Along the way, I cleaned up the error catching and reporting system. There was a lot of duplicate error logic (that wasn't always right) and some legacy Atom error handling. Now, if you catch an error that we should report (like when handling extensions), call `NylasEnv.reportError`. This used to be called `emitError` but I changed it to `reportError` to be consistent with the ErrorReporter and be a bit more indicative of what it does. In the production version, the `ErrorLogger` will forward the request to the `nylas-private-error-reporter` which will report to Sentry. The `reportError` function also now inspects the stack to determine which plugin(s) it came from. These are passed along to Sentry. I also cleaned up the `console.log` and `console.error` code. We were logging errors multiple times making the console confusing to read. Worse is that we were logging the `error` object, which would print not the stack of the actual error, but rather the stack of where the console.error was logged from. Printing `error.stack` instead shows much more accurate stack traces. See changes in the Edgehill repo here: https://github.com/nylas/edgehill/commit/8c4a86eb7ee1a06249a9ae35397e2084a09ad1dc Test Plan: Manual Reviewers: juan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2509
2016-02-04 07:06:52 +08:00
spyOn(NylasEnv, 'reportError')
spyOn(NylasAPI, 'makeRequest').andCallFake ->
Promise.reject(new APIError({statusCode: 403}))
it "persists the category and notifies error", ->
waitsForPromise ->
task = makeTask()
spyOn(task, "_notifyUserOfError")
task.performRemote().then (status) ->
expect(status).toEqual Task.Status.Failed
expect(task._notifyUserOfError).toHaveBeenCalled()
feat(error): improve error reporting. Now `NylasEnv.reportError` Summary: The goal is to let us see what plugins are throwing errors on Sentry. We are using a Sentry `tag` to identify and group plugins and their errors. Along the way, I cleaned up the error catching and reporting system. There was a lot of duplicate error logic (that wasn't always right) and some legacy Atom error handling. Now, if you catch an error that we should report (like when handling extensions), call `NylasEnv.reportError`. This used to be called `emitError` but I changed it to `reportError` to be consistent with the ErrorReporter and be a bit more indicative of what it does. In the production version, the `ErrorLogger` will forward the request to the `nylas-private-error-reporter` which will report to Sentry. The `reportError` function also now inspects the stack to determine which plugin(s) it came from. These are passed along to Sentry. I also cleaned up the `console.log` and `console.error` code. We were logging errors multiple times making the console confusing to read. Worse is that we were logging the `error` object, which would print not the stack of the actual error, but rather the stack of where the console.error was logged from. Printing `error.stack` instead shows much more accurate stack traces. See changes in the Edgehill repo here: https://github.com/nylas/edgehill/commit/8c4a86eb7ee1a06249a9ae35397e2084a09ad1dc Test Plan: Manual Reviewers: juan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2509
2016-02-04 07:06:52 +08:00
expect(NylasEnv.reportError).toHaveBeenCalled()
feat(transactions): Explicit (and faster) database transactions Summary: Until now, we've been hiding transactions beneath the surface. When you call persistModel, you're implicitly creating a transaction. You could explicitly create them with `atomically`..., but there were several critical problems that are fixed in this diff: - Calling persistModel / unpersistModel within a transaction could cause the DatabaseStore to trigger. This could result in other parts of the app making queries /during/ the transaction, potentially before the COMMIT occurred and saved the changes. The new, explicit inTransaction syntax holds all changes until after COMMIT and then triggers. - Calling atomically and then calling persistModel inside that resulted in us having to check whether a transaction was present and was gross. - Many parts of the code ran extensive logic inside a promise chained within `atomically`: BAD: ``` DatabaseStore.atomically => DatabaseStore.persistModel(draft) => GoMakeANetworkRequestThatReturnsAPromise ``` OVERWHELMINGLY BETTER: ``` DatabaseStore.inTransaction (t) => t.persistModel(draft) .then => GoMakeANetworkRequestThatReturnsAPromise ``` Having explicit transactions also puts us on equal footing with Sequelize and other ORMs. Note that you /have/ to call DatabaseStore.inTransaction (t) =>. There is no other way to access the methods that let you alter the database. :-) Other changes: - This diff removes Message.labels and the Message-Labels table. We weren't using Message-level labels anywhere, and the table could grow very large. - This diff changes the page size during initial sync from 250 => 200 in an effort to make transactions a bit faster. Test Plan: Run tests! Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D2353
2015-12-18 03:46:05 +08:00
expect(DatabaseTransaction.prototype.persistModel).toHaveBeenCalled()
model = DatabaseTransaction.prototype.persistModel.calls[0].args[0]
expect(model.serverId).toEqual "server-444"
expect(NylasAPI.decrementRemoteChangeLock).toHaveBeenCalled
describe "_notifyUserOfError", ->
it "should present an error dialog", ->
spyOn(NylasEnv, 'showErrorDialog')
task = makeTask()
task._notifyUserOfError(task.category)
expect(NylasEnv.showErrorDialog).toHaveBeenCalled()