2015-08-27 02:43:10 +08:00
_ = require ' underscore '
fs = require ' fs '
Actions = require ' ../src/flux/actions '
NylasAPI = require ' ../src/flux/nylas-api '
Thread = require ' ../src/flux/models/thread '
2016-03-17 10:27:12 +08:00
Message = require ' ../src/flux/models/message '
2016-02-24 10:35:24 +08:00
AccountStore = require ' ../src/flux/stores/account-store '
2015-08-27 02:43:10 +08:00
DatabaseStore = require ' ../src/flux/stores/database-store '
2015-12-18 03:46:05 +08:00
DatabaseTransaction = require ' ../src/flux/stores/database-transaction '
2015-08-27 02:43:10 +08:00
describe " NylasAPI " , ->
2016-03-11 03:06:06 +08:00
describe " authPlugin " , ->
beforeEach ->
NylasAPI.pluginsSupported = true
@authGetResponse = null
@authPostResponse = null
@error = null
@resolved = false
spyOn ( NylasEnv . config , ' set ' )
spyOn ( NylasEnv . config , ' get ' ) . andReturn ( null )
spyOn ( NylasAPI , ' makeRequest ' ) . andCallFake (options) =>
return @ authGetResponse if options . method is ' GET ' and @ authGetResponse
return @ authPostResponse if options . method is ' POST ' and @ authPostResponse
return new Promise (resolve, reject) -> #never respond
it " should reject if the current environment does not support plugins " , ->
NylasAPI.pluginsSupported = false
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID ) . catch (err) => @error = err
waitsFor =>
@ error
runs =>
expect ( @ error . message ) . toEqual ( ' Sorry, this feature is only available when N1 is running against the hosted version of the Nylas Sync Engine. ' )
it " should reject if no account can be found for the given accountOrId " , ->
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , ' randomAccountId ' ) . catch (err) => @error = err
waitsFor =>
@ error
runs =>
expect ( @ error . message ) . toEqual ( ' Invalid account ' )
it " should resolve if the plugin has been successfully authed with accountOrId already " , ->
jasmine . unspy ( NylasEnv . config , ' get ' )
spyOn ( NylasEnv . config , ' get ' ) . andCallFake (key) =>
return Date . now ( ) if key is " plugins.PID.lastAuth. #{ TEST_ACCOUNT_ID } "
return null
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID ) . then (err) =>
@resolved = true
waitsFor =>
@ resolved
expect ( NylasAPI . makeRequest ) . not . toHaveBeenCalled ( )
describe " check for existing auth " , ->
it " should GET /auth/plugin to check if the plugin has been authed " , ->
@authGetResponse = Promise . resolve ( { authed: true } )
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID )
advanceClock ( )
expect ( NylasAPI . makeRequest ) . toHaveBeenCalledWith ( {
returnsModel: false ,
method: ' GET ' ,
accountId: ' test-account-server-id ' ,
path: ' /auth/plugin?client_id=PID '
} )
it " should record a successful auth in the config and resolve without making a POST " , ->
@authGetResponse = Promise . resolve ( { authed: true } )
@authPostResponse = null
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID ) . then => @resolved = true
waitsFor =>
@ resolved
runs =>
expect ( NylasAPI . makeRequest ) . toHaveBeenCalled ( )
expect ( NylasEnv . config . set . mostRecentCall . args [ 0 ] ) . toEqual ( " plugins.PID.lastAuth. #{ TEST_ACCOUNT_ID } " )
it " should propagate any network errors back to the caller " , ->
@authGetResponse = Promise . reject ( new Error ( " Network failure! " ) )
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID ) . catch (err) => @error = err
advanceClock ( )
advanceClock ( )
expect ( @ error . message ) . toBe ( " Network failure! " )
expect ( NylasEnv . config . set ) . not . toHaveBeenCalled ( )
describe " request for auth " , ->
it " should POST to /auth/plugin with the client id and record a successful auth " , ->
@authGetResponse = Promise . resolve ( { authed: false } )
@authPostResponse = Promise . resolve ( { authed: true } )
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID ) . then => @resolved = true
waitsFor =>
@ resolved
runs =>
expect ( NylasAPI . makeRequest . calls [ 0 ] . args [ 0 ] ) . toEqual ( {
returnsModel: false ,
method: ' GET ' ,
accountId: ' test-account-server-id ' ,
path: ' /auth/plugin?client_id=PID '
} )
expect ( NylasAPI . makeRequest . calls [ 1 ] . args [ 0 ] ) . toEqual ( {
returnsModel: false ,
method: ' POST ' ,
accountId: ' test-account-server-id ' ,
path: ' /auth/plugin ' ,
body: { client_id: ' PID ' } ,
json: true
} )
setCall = NylasEnv . config . set . mostRecentCall
expect ( setCall . args [ 0 ] ) . toEqual ( " plugins.PID.lastAuth. #{ TEST_ACCOUNT_ID } " )
it " should propagate any network errors back to the caller " , ->
@authGetResponse = Promise . resolve ( { authed: false } )
@authPostResponse = Promise . reject ( new Error ( " Network failure! " ) )
NylasAPI . authPlugin ( ' PID ' , ' PSECRET ' , TEST_ACCOUNT_ID ) . catch (err) => @error = err
waitsFor =>
@ error
runs =>
expect ( @ error . message ) . toBe ( " Network failure! " )
2015-08-27 02:43:10 +08:00
describe " handleModel404 " , ->
it " should unpersist the model from the cache that was requested " , ->
model = new Thread ( id: ' threadidhere ' )
2015-12-18 03:46:05 +08:00
spyOn ( DatabaseTransaction . prototype , ' unpersistModel ' )
2015-08-27 02:43:10 +08:00
spyOn ( DatabaseStore , ' find ' ) . andCallFake (klass, id) =>
return Promise . resolve ( model )
NylasAPI . _handleModel404 ( " /threads/ #{ model . id } " )
advanceClock ( )
expect ( DatabaseStore . find ) . toHaveBeenCalledWith ( Thread , model . id )
2015-12-18 03:46:05 +08:00
expect ( DatabaseTransaction . prototype . unpersistModel ) . toHaveBeenCalledWith ( model )
2015-08-27 02:43:10 +08:00
it " should not do anything if the model is not in the cache " , ->
2015-12-18 03:46:05 +08:00
spyOn ( DatabaseTransaction . prototype , ' unpersistModel ' )
2015-08-27 02:43:10 +08:00
spyOn ( DatabaseStore , ' find ' ) . andCallFake (klass, id) =>
return Promise . resolve ( null )
NylasAPI . _handleModel404 ( " /threads/1234 " )
advanceClock ( )
expect ( DatabaseStore . find ) . toHaveBeenCalledWith ( Thread , ' 1234 ' )
2015-12-18 03:46:05 +08:00
expect ( DatabaseTransaction . prototype . unpersistModel ) . not . toHaveBeenCalledWith ( )
2015-08-27 02:43:10 +08:00
it " should not do anything bad if it doesn ' t recognize the class " , ->
spyOn ( DatabaseStore , ' find ' )
2015-12-18 03:46:05 +08:00
spyOn ( DatabaseTransaction . prototype , ' unpersistModel ' )
2015-08-27 02:43:10 +08:00
waitsForPromise ->
NylasAPI . _handleModel404 ( " /asdasdasd/1234 " )
runs ->
expect ( DatabaseStore . find ) . not . toHaveBeenCalled ( )
2015-12-18 03:46:05 +08:00
expect ( DatabaseTransaction . prototype . unpersistModel ) . not . toHaveBeenCalled ( )
2015-08-27 02:43:10 +08:00
it " should not do anything bad if the endpoint only has a single segment " , ->
spyOn ( DatabaseStore , ' find ' )
2015-12-18 03:46:05 +08:00
spyOn ( DatabaseTransaction . prototype , ' unpersistModel ' )
2015-08-27 02:43:10 +08:00
waitsForPromise ->
NylasAPI . _handleModel404 ( " /account " )
runs ->
expect ( DatabaseStore . find ) . not . toHaveBeenCalled ( )
2015-12-18 03:46:05 +08:00
expect ( DatabaseTransaction . prototype . unpersistModel ) . not . toHaveBeenCalled ( )
2015-08-27 02:43:10 +08:00
2016-02-24 10:35:24 +08:00
describe " handleAuthenticationFailure " , ->
2015-08-27 02:43:10 +08:00
it " should post a notification " , ->
spyOn ( Actions , ' postNotification ' )
2016-02-24 10:35:24 +08:00
NylasAPI . _handleAuthenticationFailure ( ' /threads/1234 ' , ' token ' )
2015-08-27 02:43:10 +08:00
expect ( Actions . postNotification ) . toHaveBeenCalled ( )
2016-03-09 23:03:05 +08:00
expect ( Actions . postNotification . mostRecentCall . args [ 0 ] . message . trim ( ) ) . toEqual ( " Action failed: There was an error syncing with your mail provider. You may not be able to send or receive mail. " )
2016-02-24 10:35:24 +08:00
it " should include the email address if possible " , ->
spyOn ( AccountStore , ' tokenForAccountId ' ) . andReturn ( ' token ' )
spyOn ( Actions , ' postNotification ' )
NylasAPI . _handleAuthenticationFailure ( ' /threads/1234 ' , ' token ' )
expect ( Actions . postNotification ) . toHaveBeenCalled ( )
2016-03-09 23:03:05 +08:00
expect ( Actions . postNotification . mostRecentCall . args [ 0 ] . message . trim ( ) ) . toEqual ( " Action failed: There was an error syncing with #{ AccountStore . accounts ( ) [ 0 ] . emailAddress } . You may not be able to send or receive mail. " )
2015-08-27 02:43:10 +08:00
2015-09-03 03:22:20 +08:00
describe " handleModelResponse " , ->
beforeEach ->
2015-12-18 03:46:05 +08:00
spyOn ( DatabaseTransaction . prototype , " persistModels " ) . andCallFake (models) ->
2015-09-03 03:22:20 +08:00
Promise . resolve ( models )
stubDB = ({models, testClass, testMatcher}) ->
spyOn ( DatabaseStore , " findAll " ) . andCallFake (klass) ->
testClass ? ( klass )
where: (matcher) ->
testMatcher ? ( matcher )
Promise . resolve ( models )
2015-08-27 02:43:10 +08:00
it " should reject if no JSON is provided " , ->
2015-09-03 03:22:20 +08:00
waitsForPromise ->
NylasAPI . _handleModelResponse ( )
. then -> throw new Error ( " Should reject! " )
. catch (err) ->
expect ( err . message ) . toEqual " handleModelResponse with no JSON provided "
2015-08-27 02:43:10 +08:00
it " should resolve if an empty JSON array is provided " , ->
2015-09-03 03:22:20 +08:00
waitsForPromise ->
NylasAPI . _handleModelResponse ( [ ] )
. then (resp) ->
expect ( resp ) . toEqual [ ]
describe " if JSON contains objects which are of unknown types " , ->
it " should warn and resolve " , ->
spyOn ( console , " warn " )
waitsForPromise ->
NylasAPI . _handleModelResponse ( [ { id: ' a ' , object: ' unknown ' } ] )
. then (resp) ->
expect ( resp ) . toEqual [ ]
expect ( console . warn ) . toHaveBeenCalled ( )
expect ( console . warn . calls . length ) . toBe 1
2015-08-27 02:43:10 +08:00
describe " if JSON contains the same object more than once " , ->
2015-09-03 03:22:20 +08:00
beforeEach ->
stubDB ( models: [ ] )
spyOn ( console , " warn " )
@dupes = [
{ id: ' a ' , object: ' thread ' }
{ id: ' a ' , object: ' thread ' }
{ id: ' b ' , object: ' thread ' }
]
2015-08-27 02:43:10 +08:00
it " should warn " , ->
2015-09-03 03:22:20 +08:00
waitsForPromise =>
NylasAPI . _handleModelResponse ( @ dupes )
. then ->
expect ( console . warn ) . toHaveBeenCalled ( )
expect ( console . warn . calls . length ) . toBe 1
2015-08-27 02:43:10 +08:00
it " should omit duplicates " , ->
2015-09-03 03:22:20 +08:00
waitsForPromise =>
NylasAPI . _handleModelResponse ( @ dupes )
. then ->
2015-12-18 03:46:05 +08:00
models = DatabaseTransaction . prototype . persistModels . calls [ 0 ] . args [ 0 ]
2015-09-03 03:22:20 +08:00
expect ( models . length ) . toBe 2
expect ( models [ 0 ] . id ) . toBe ' a '
expect ( models [ 1 ] . id ) . toBe ' b '
2015-08-27 02:43:10 +08:00
2016-03-04 04:09:16 +08:00
describe " when items in the JSON are locked and we are not accepting changes to them " , ->
2015-09-03 03:22:20 +08:00
it " should remove locked models from the set " , ->
json = [
{ id: ' a ' , object: ' thread ' }
{ id: ' b ' , object: ' thread ' }
]
2016-03-04 04:09:16 +08:00
spyOn ( NylasAPI . _lockTracker , " acceptRemoteChangesTo " ) . andCallFake (klass, id) ->
2015-09-03 03:22:20 +08:00
if id is " a " then return false
stubDB models: [ new Thread ( json [ 1 ] ) ] , testMatcher: (whereMatcher) ->
2016-01-26 04:11:57 +08:00
expect ( whereMatcher . val ) . toEqual ' b '
2015-09-03 03:22:20 +08:00
waitsForPromise =>
NylasAPI . _handleModelResponse ( json )
. then (models) ->
expect ( models . length ) . toBe 1
2015-12-18 03:46:05 +08:00
models = DatabaseTransaction . prototype . persistModels . calls [ 0 ] . args [ 0 ]
2015-09-03 03:22:20 +08:00
expect ( models . length ) . toBe 1
expect ( models [ 0 ] . id ) . toBe ' b '
describe " when updating models " , ->
Message = require ' ../src/flux/models/message '
beforeEach ->
@json = [
{ id: ' a ' , object: ' draft ' , unread: true }
{ id: ' b ' , object: ' draft ' , starred: true }
]
@existing = new Message ( id: ' b ' , unread: true )
stubDB models: [ @ existing ]
verifyUpdateHappened = (responseModels) ->
2015-12-18 03:46:05 +08:00
changedModels = DatabaseTransaction . prototype . persistModels . calls [ 0 ] . args [ 0 ]
2015-09-03 03:22:20 +08:00
expect ( changedModels . length ) . toBe 2
expect ( changedModels [ 1 ] . id ) . toBe ' b '
expect ( changedModels [ 1 ] . starred ) . toBe true
# Doesn't override existing values
expect ( changedModels [ 1 ] . unread ) . toBe true
expect ( responseModels . length ) . toBe 2
expect ( responseModels [ 0 ] . id ) . toBe ' a '
expect ( responseModels [ 0 ] . unread ) . toBe true
it " updates found models with new data " , ->
waitsForPromise =>
NylasAPI . _handleModelResponse ( @ json ) . then verifyUpdateHappened
it " updates if the json version is newer " , ->
@existing.version = 9
@ json [ 1 ] . version = 10
waitsForPromise =>
NylasAPI . _handleModelResponse ( @ json ) . then verifyUpdateHappened
verifyUpdateStopped = (responseModels) ->
2015-12-18 03:46:05 +08:00
changedModels = DatabaseTransaction . prototype . persistModels . calls [ 0 ] . args [ 0 ]
2015-09-03 03:22:20 +08:00
expect ( changedModels . length ) . toBe 1
expect ( changedModels [ 0 ] . id ) . toBe ' a '
expect ( changedModels [ 0 ] . unread ) . toBe true
expect ( responseModels . length ) . toBe 2
expect ( responseModels [ 1 ] . id ) . toBe ' b '
expect ( responseModels [ 1 ] . starred ) . toBeUndefined ( )
it " doesn ' t update if the json version is older " , ->
@existing.version = 10
@ json [ 1 ] . version = 9
waitsForPromise =>
NylasAPI . _handleModelResponse ( @ json ) . then verifyUpdateStopped
it " doesn ' t update if it ' s already sent " , ->
@existing.draft = false
@ json [ 1 ] . draft = true
waitsForPromise =>
NylasAPI . _handleModelResponse ( @ json ) . then verifyUpdateStopped
2015-08-27 02:43:10 +08:00
2015-09-03 03:22:20 +08:00
describe " handling all types of objects " , ->
apiObjectToClassMap =
" file " : require ( ' ../src/flux/models/file ' )
" event " : require ( ' ../src/flux/models/event ' )
" label " : require ( ' ../src/flux/models/label ' )
" folder " : require ( ' ../src/flux/models/folder ' )
" thread " : require ( ' ../src/flux/models/thread ' )
" draft " : require ( ' ../src/flux/models/message ' )
" account " : require ( ' ../src/flux/models/account ' )
" message " : require ( ' ../src/flux/models/message ' )
" contact " : require ( ' ../src/flux/models/contact ' )
" calendar " : require ( ' ../src/flux/models/calendar ' )
2015-08-27 02:43:10 +08:00
2015-09-03 03:22:20 +08:00
verifyUpdateHappened = (klass, responseModels) ->
2015-12-18 03:46:05 +08:00
changedModels = DatabaseTransaction . prototype . persistModels . calls [ 0 ] . args [ 0 ]
2015-09-03 03:22:20 +08:00
expect ( changedModels . length ) . toBe 2
expect ( changedModels [ 0 ] . id ) . toBe ' a '
expect ( changedModels [ 1 ] . id ) . toBe ' b '
expect ( changedModels [ 0 ] instanceof klass ) . toBe true
expect ( changedModels [ 1 ] instanceof klass ) . toBe true
expect ( responseModels . length ) . toBe 2
expect ( responseModels [ 0 ] . id ) . toBe ' a '
expect ( responseModels [ 1 ] . id ) . toBe ' b '
expect ( responseModels [ 0 ] instanceof klass ) . toBe true
expect ( responseModels [ 1 ] instanceof klass ) . toBe true
2015-08-27 02:43:10 +08:00
2015-09-03 03:22:20 +08:00
_ . forEach apiObjectToClassMap , (klass, type) ->
it " properly handle the ' #{ type } ' type " , ->
json = [
{ id: ' a ' , object: type }
{ id: ' b ' , object: type }
]
stubDB models: [ new klass ( id: ' b ' ) ]
2015-08-27 02:43:10 +08:00
2015-09-03 03:22:20 +08:00
verifyUpdate = _ . partial ( verifyUpdateHappened , klass )
waitsForPromise =>
NylasAPI . _handleModelResponse ( json ) . then verifyUpdate
2016-03-17 10:27:12 +08:00
describe " makeDraftDeletionRequest " , ->
it " should make an API request to delete the draft " , ->
draft = new Message ( accountId: TEST_ACCOUNT_ID , draft: true , clientId: ' asd ' , serverId: ' asd ' )
spyOn ( NylasAPI , ' makeRequest ' )
NylasAPI . makeDraftDeletionRequest ( draft )
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 increment the change tracker, preventing any further deltas about the draft " , ->
draft = new Message ( accountId: TEST_ACCOUNT_ID , draft: true , clientId: ' asd ' , serverId: ' asd ' )
spyOn ( NylasAPI , ' incrementRemoteChangeLock ' )
NylasAPI . makeDraftDeletionRequest ( draft )
expect ( NylasAPI . incrementRemoteChangeLock ) . toHaveBeenCalledWith ( Message , draft . serverId )
it " should not return a promise or anything else, to avoid accidentally making things dependent on the request " , ->
draft = new Message ( accountId: TEST_ACCOUNT_ID , draft: true , clientId: ' asd ' , serverId: ' asd ' )
a = NylasAPI . makeDraftDeletionRequest ( draft )
expect ( a ) . toBe ( undefined )
it " should not do anything if the draft is missing a serverId " , ->
draft = new Message ( accountId: TEST_ACCOUNT_ID , draft: true , clientId: ' asd ' , serverId: null )
spyOn ( NylasAPI , ' makeRequest ' )
NylasAPI . makeDraftDeletionRequest ( draft )
expect ( NylasAPI . makeRequest ) . not . toHaveBeenCalled ( )