From 45058553ce8a5873e1c948712a94a70a7efd30b7 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 11:26:42 -0700 Subject: [PATCH 01/24] Add JSON Accept header for JSON responses - add request debugging --- README.md | 2 ++ lib/request.coffee | 27 +++++++++++++++++---------- package.json | 1 + 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c507a3f..a9b9c99 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # node-intuit `npm` port of https://github.com/cloocher/aggcat + +Debug with `DEBUG=intuit*`. diff --git a/lib/request.coffee b/lib/request.coffee index 4334b68..09b05ee 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -1,4 +1,5 @@ request = require "request" +debug = require("debug")("node-intuit:request") OAuth = require "./oauth" SAML_URL = "https://oauth.intuit.com/oauth/v1/get_access_token_by_saml" @@ -14,6 +15,7 @@ module.exports = class Request uri: url headers: "Content-Type": "application/json" + "Accept": "application/json" oauth: consumer_key: @options.consumerKey consumer_secret: @options.consumerSecret @@ -21,24 +23,29 @@ module.exports = class Request token_secret: tokenSecret done err, params + request: (params, done) -> + request params, (err, response, body) -> + debug "#{response.statusCode} - #{response.statusMessage} - #{response.request.uri.path}" + done err, body + get: (url, body, done) -> - @_params "GET", url, (err, params) -> + @_params "GET", url, (err, params) => return done err if err - request params, done + @request params, done post: (url, body, done) -> - @_params "POST", url, (err, params) -> + @_params "POST", url, (err, params) => return done err if err - params.body = body - request params, done + params.form = body + @request params, done put: (url, body, done) -> - @_params "PUT", url, (err, params) -> + @_params "PUT", url, (err, params) => return done err if err - params.body = body - request params, done + params.form = body + @request params, done delete: (url, body, done) -> - @_params "DELETE", url, (err, params) -> + @_params "DELETE", url, (err, params) => return done err if err - request params, done + @request params, done diff --git a/package.json b/package.json index 1a07cfc..3529f57 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "coffee-script": "^1.9.3", + "debug": "^2.2.0", "node-uuid": "^1.4.3", "request": "^2.59.0" }, From 7e037d1986507c194b2a28c44b6b2e3469cf3739 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 11:32:39 -0700 Subject: [PATCH 02/24] Add test for JSON headers --- test/api.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/api.coffee b/test/api.coffee index 52b7e53..5b7bde3 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -14,11 +14,13 @@ describe "Intuit API Client", -> assert.notEqual intuit.institutions, undefined describe "getInstitutionDetails", -> - it "should should send a signed request", (done) -> + it "should should send a signed JSON request", (done) -> institutionDetails = name: "Bank Name" spy = bond(http, "request").through() intuit.getInstitutionDetails 100000, (err, @institutionDetails) -> + assert.equal spy.calledArgs[0][0].headers["Content-Type"], "application/json" + assert.equal spy.calledArgs[0][0].headers["Accept"], "application/json" assert /oauth_signature/.test spy.calledArgs[0][0].headers.Authorization done err From f5b41818852a64cefd11a163a14b96c438e71cfd Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 15:23:33 -0700 Subject: [PATCH 03/24] Parse response body as valid JSON --- lib/request.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/request.coffee b/lib/request.coffee index 09b05ee..90983ff 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -26,7 +26,12 @@ module.exports = class Request request: (params, done) -> request params, (err, response, body) -> debug "#{response.statusCode} - #{response.statusMessage} - #{response.request.uri.path}" - done err, body + try + parsed = JSON.parse body + catch e + err = e + parsed = body + done err, parsed get: (url, body, done) -> @_params "GET", url, (err, params) => From bb53f8eb1da6a376c49057e0b662d1dd8565a248 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 17:33:00 -0700 Subject: [PATCH 04/24] Add nock fixture recording to assert API responses --- lib/request.coffee | 3 +- test/api.coffee | 32 +++++++------ test/fixtures/getInstitutionDetails.json | 58 ++++++++++++++++++++++++ test/fixtures/index.coffee | 36 +++++++++++++++ test/helper.coffee | 9 ---- test/lib/oauth.coffee | 4 +- 6 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/getInstitutionDetails.json create mode 100644 test/fixtures/index.coffee delete mode 100644 test/helper.coffee diff --git a/lib/request.coffee b/lib/request.coffee index 90983ff..9bc3ca4 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -24,7 +24,8 @@ module.exports = class Request done err, params request: (params, done) -> - request params, (err, response, body) -> + requestMethod = request[params.method.toLowerCase()] + requestMethod params, (err, response, body) -> debug "#{response.statusCode} - #{response.statusMessage} - #{response.request.uri.path}" try parsed = JSON.parse body diff --git a/test/api.coffee b/test/api.coffee index 5b7bde3..da63ef1 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -1,12 +1,11 @@ assert = require "assert" -http = require "http" +request = require "request" bond = require "bondjs" request = require "request" intuitClient = require "../" config = require "./config" intuit = intuitClient config -helper = require "./helper" -helper.stubOAuth() +fixture = require "./fixtures" describe "Intuit API Client", -> describe "institutions", -> @@ -14,18 +13,25 @@ describe "Intuit API Client", -> assert.notEqual intuit.institutions, undefined describe "getInstitutionDetails", -> - it "should should send a signed JSON request", (done) -> - institutionDetails = - name: "Bank Name" - spy = bond(http, "request").through() - intuit.getInstitutionDetails 100000, (err, @institutionDetails) -> - assert.equal spy.calledArgs[0][0].headers["Content-Type"], "application/json" - assert.equal spy.calledArgs[0][0].headers["Accept"], "application/json" - assert /oauth_signature/.test spy.calledArgs[0][0].headers.Authorization + before -> + fixture.load "getInstitutionDetails" + @spy = bond(request, "get").through() + before (done) -> + intuit.getInstitutionDetails 100000, (err, @institutionDetails) => done err - it "should be defined", -> - assert.notEqual intuit.getInstitutionDetails, undefined + it "should request JSON", -> + assert.equal @spy.calledArgs[0][0].headers["Content-Type"], "application/json" + assert.equal @spy.calledArgs[0][0].headers["Accept"], "application/json" + + it "should use OAuth data to sign request", -> + assert @spy.calledArgs[0][0].oauth.consumer_key + assert @spy.calledArgs[0][0].oauth.consumer_secret + assert @spy.calledArgs[0][0].oauth.token + assert @spy.calledArgs[0][0].oauth.token_secret + + it "should return institution details", -> + assert.equal @institutionDetails.institutionName, "CCBank-Beacon" describe "discoverAndAddAccounts", -> it "should be defined", -> diff --git a/test/fixtures/getInstitutionDetails.json b/test/fixtures/getInstitutionDetails.json new file mode 100644 index 0000000..5048a05 --- /dev/null +++ b/test/fixtures/getInstitutionDetails.json @@ -0,0 +1,58 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/institutions/100000", + "body": "", + "status": 200, + "response": { + "address": { + "country": "USA" + }, + "keys": { + "key": [ + { + "name": "Banking Password", + "description": "Banking Password", + "displayOrder": 2, + "status": "Active", + "valueLengthMin": 1, + "valueLengthMax": 20, + "displayFlag": true, + "instructions": "Enter banking password (go)", + "mask": true + }, + { + "name": "Banking Userid", + "description": "Banking Userid", + "displayOrder": 1, + "status": "Active", + "valueLengthMin": 1, + "valueLengthMax": 20, + "displayFlag": true, + "instructions": "Enter banking userid (demo)", + "mask": false + } + ] + }, + "currencyCode": "USD", + "specialText": "Please enter your intuit.com Account Number and Password required for login.", + "homeUrl": "http://www.intuit.com", + "institutionId": 100000, + "institutionName": "CCBank-Beacon" + }, + "headers": { + "date": "Tue, 28 Jul 2015 22:39:38 GMT", + "pragma": "no-cache", + "content-type": "application/json", + "content-length": "670", + "cache-control": "no-cache", + "expires": "Wed, 31 Dec 1969 16:00:00 PST", + "intuit_tid": "gw-8937219d-6a7f-4543-bd11-f52eb63a09c6", + "set-cookie": [ + "JSESSIONID=80381852D0F879D03D6E539E65F273E4.nodepprdccoap40w.corp.intuit.net; Path=/rest-war" + ], + "connection": "close" + } + } +] \ No newline at end of file diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee new file mode 100644 index 0000000..f7aba27 --- /dev/null +++ b/test/fixtures/index.coffee @@ -0,0 +1,36 @@ +_ = require "lodash" +nock = require "nock" +path = require "path" +fs = require "fs" + +load = (fileName) -> + nock.load path.resolve __dirname, "./#{fileName}.json" + +oauth = -> + nock("https://oauth.intuit.com:443") + .filteringRequestBody((body) -> return "SAML") + .post("/oauth/v1/get_access_token_by_saml", "SAML") + .reply(200, "oauth_token_secret=L63cI4q5UhP4mQzpCi1RHBMSLe2TNOaI98vxyIBL&oauth_token=qyprdcvDh5V2XoJRwYmxxdL5vOJ54Z6sNVohlNLQHyyhHaAy") + +fixtures = + oauth: oauth + getInstitutionDetails: -> + oauth() + load "getInstitutionDetails" + + +module.exports = + record: -> + nock.recorder.rec + output_objects: true + dont_print: true + + save: (fileName) -> + fs.writeFileSync path.resolve(__dirname, "./#{fileName}.json"), JSON.stringify(nock.recorder.play(), null, 2) + + load: (fixture, times=1) -> + mock = fixtures[fixture] + loadMock = -> + return mock() if mock + load fixture + _.times times, loadMock diff --git a/test/helper.coffee b/test/helper.coffee deleted file mode 100644 index 0b929c1..0000000 --- a/test/helper.coffee +++ /dev/null @@ -1,9 +0,0 @@ -nock = require "nock" - -module.exports = - stubOAuth: (times = 1) -> - nock("https://oauth.intuit.com:443") - .filteringRequestBody((body) -> return "SAML") - .post("/oauth/v1/get_access_token_by_saml", "SAML") - .times(times) - .reply(200, "oauth_token_secret=L63cI4q5UhP4mQzpCi1RHBMSLe2TNOaI98vxyIBL&oauth_token=qyprdcvDh5V2XoJRwYmxxdL5vOJ54Z6sNVohlNLQHyyhHaAy") diff --git a/test/lib/oauth.coffee b/test/lib/oauth.coffee index 4e3b547..1051ee2 100644 --- a/test/lib/oauth.coffee +++ b/test/lib/oauth.coffee @@ -3,12 +3,12 @@ bond = require "bondjs" request = require "request" OAuth = require "../../lib/oauth" config = require "../config" -helper = require "../helper" -helper.stubOAuth(3) +fixture = require "../fixtures" describe "OAuth", -> describe "getToken", -> before -> @spy = bond(request, "post").through() + before -> fixture.load "oauth", 3 before (done) -> oauth = new OAuth config oauth.getToken (@err, @token, @secret) => From f17d95694cfdd1af3a74c67dd7a43a29ff1011e7 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 17:38:06 -0700 Subject: [PATCH 05/24] Add lodash dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3529f57..afd3796 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "coffee-script": "^1.9.3", "debug": "^2.2.0", + "lodash": "^3.10.0", "node-uuid": "^1.4.3", "request": "^2.59.0" }, From 39383ad1674f51e484fc5fc180df29a7b597ab87 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 19:40:20 -0700 Subject: [PATCH 06/24] Add more tests for various methods - skip pending tests for more obvious TODO --- lib/client.coffee | 37 ++++++--- test/api.coffee | 105 ++++++++++++++----------- test/config.coffee | 1 - test/fixtures/getCustomerAccounts.json | 55 +++++++++++++ test/fixtures/index.coffee | 15 ++++ 5 files changed, 155 insertions(+), 58 deletions(-) create mode 100644 test/fixtures/getCustomerAccounts.json diff --git a/lib/client.coffee b/lib/client.coffee index ba46257..2d3a5a7 100644 --- a/lib/client.coffee +++ b/lib/client.coffee @@ -9,29 +9,42 @@ module.exports = class IntuitClient [body, done] = [{}, body] if typeof(body) is "function" new Request(@options)[method]("#{BASE_URL}#{path}", body, done) - institutions: (done) -> - @request "get", "/institutions", done - - getInstitutionDetails: (institutionId, done) -> + institutions: (userId, done) -> + @options.userId = userId + @request "get", "/institutions", (err, response) -> + return done err if err + done err, response.institution + + getInstitutionDetails: (userId, institutionId, done) -> + @options.userId = userId @request "get", "/institutions/#{institutionId}", done - discoverAndAddAccounts: (institutionId, credentials, done) -> + discoverAndAddAccounts: (userId, institutionId, credentials, done) -> + @options.userId = userId @request "post", "/institutions/#{institutionId}/logins", credentials, done - getCustomerAccounts: (done) -> - @request "get", "/accounts", done + getCustomerAccounts: (userId, done) -> + @options.userId = userId + @request "get", "/accounts", (err, response) -> + return done err if err + done err, response.accounts - getAccount: (accountId, done) -> + getAccount: (userId, accountId, done) -> + @options.userId = userId @request "get", "/accounts/#{accountId}", done - getAccountTransactions: (accountId, startDate, endDate) -> + getAccountTransactions: (userId, accountId, startDate, endDate) -> + @options.userId = userId @request "get", "/accounts/#{accountId}/transactions", done - updateInstitutionLogin: (institutionId, loginId, credentials, done) -> + updateInstitutionLogin: (userId, institutionId, loginId, credentials, done) -> + @options.userId = userId @request "put", "/logins/#{loginId}?refresh=true", credentials, done - deleteAccount: (accountId, done) -> + deleteAccount: (userId, accountId, done) -> + @options.userId = userId @request "delete", "/accounts/#{accountId}", done - deleteCustomer: -> + deleteCustomer: (userId, done) -> + @options.userId = userId @request "delete", "/customers", done diff --git a/test/api.coffee b/test/api.coffee index da63ef1..88e1721 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -7,60 +7,75 @@ config = require "./config" intuit = intuitClient config fixture = require "./fixtures" -describe "Intuit API Client", -> - describe "institutions", -> - it "should be defined", -> - assert.notEqual intuit.institutions, undefined +describe "Intuit Client", -> + describe "Data", -> + describe "headers", -> + before -> + fixture.load "signedJson" + before (done) -> + intuit.getInstitutionDetails "userId", 100000, (@err) => + done null - describe "getInstitutionDetails", -> - before -> - fixture.load "getInstitutionDetails" - @spy = bond(request, "get").through() - before (done) -> - intuit.getInstitutionDetails 100000, (err, @institutionDetails) => - done err + it "should make a signed request for JSON", -> + assert.equal @err, null - it "should request JSON", -> - assert.equal @spy.calledArgs[0][0].headers["Content-Type"], "application/json" - assert.equal @spy.calledArgs[0][0].headers["Accept"], "application/json" + describe "API", -> + describe "institutions", -> + it "should be defined", -> + assert.notEqual intuit.institutions, undefined - it "should use OAuth data to sign request", -> - assert @spy.calledArgs[0][0].oauth.consumer_key - assert @spy.calledArgs[0][0].oauth.consumer_secret - assert @spy.calledArgs[0][0].oauth.token - assert @spy.calledArgs[0][0].oauth.token_secret + describe "getInstitutionDetails", -> + before -> + fixture.load "getInstitutionDetails" + @spy = bond(request, "get").through() + before (done) -> + intuit.getInstitutionDetails "userId", 100000, (err, @institutionDetails) => + done err - it "should return institution details", -> - assert.equal @institutionDetails.institutionName, "CCBank-Beacon" + it "should use OAuth data to sign request", -> + assert @spy.calledArgs[0][0].oauth.consumer_key + assert @spy.calledArgs[0][0].oauth.consumer_secret + assert @spy.calledArgs[0][0].oauth.token + assert @spy.calledArgs[0][0].oauth.token_secret - describe "discoverAndAddAccounts", -> - it "should be defined", -> - assert.notEqual intuit.discoverAndAddAccounts, undefined + it "should return institution details", -> + assert.equal @institutionDetails.institutionName, "CCBank-Beacon" - describe "mfa", -> - it.skip "should be defined", -> - assert.notEqual intuit.mfa, undefined + describe.skip "discoverAndAddAccounts", -> + it "should return newly created accounts", -> + assert.notEqual intuit.discoverAndAddAccounts, undefined - describe "getCustomerAccounts", -> - it "should be defined", -> - assert.notEqual intuit.getCustomerAccounts, undefined + describe.skip "mfa", -> + it "should handle MFA", -> + assert.notEqual intuit.mfa, undefined - describe "getAccount", -> - it "should be defined", -> - assert.notEqual intuit.getAccount, undefined + describe "getCustomerAccounts", -> + before -> + fixture.load "oauth" + fixture.load "getCustomerAccounts" + before (done) -> + intuit.getCustomerAccounts "todd", (err, @accounts) => + done err - describe "getAccountTransactions", -> - it "should be defined", -> - assert.notEqual intuit.getAccountTransactions, undefined + it "should return all accounts", -> + assert.equal @accounts.length, 2 - describe "updateInstitutionLogin", -> - it "should be defined", -> - assert.notEqual intuit.updateInstitutionLogin, undefined + describe.skip "getAccount", -> + it "should return an account", -> + assert.notEqual intuit.getAccount, undefined - describe "deleteAccount", -> - it "should be defined", -> - assert.notEqual intuit.deleteAccount, undefined + describe.skip "getAccountTransactions", -> + it "should return transactions", -> + assert.notEqual intuit.getAccountTransactions, undefined - describe "deleteCustomer", -> - it "should be defined", -> - assert.notEqual intuit.deleteCustomer, undefined + describe.skip "updateInstitutionLogin", -> + it "should update bank credentials", -> + assert.notEqual intuit.updateInstitutionLogin, undefined + + describe.skip "deleteAccount", -> + it "should remove the account", -> + assert.notEqual intuit.deleteAccount, undefined + + describe.skip "deleteCustomer", -> + it "should remove the user", -> + assert.notEqual intuit.deleteCustomer, undefined diff --git a/test/config.coffee b/test/config.coffee index e5e654e..ada9d5c 100644 --- a/test/config.coffee +++ b/test/config.coffee @@ -5,7 +5,6 @@ module.exports = issuerId: "issuer-id" consumerKey: "consumer-key" consumerSecret: "consumer-secret" - userId: "1" # We only have up to 10 in development mode key: fs.readFileSync path.join(__dirname, "test.key"), "utf8" certificate: fs.readFileSync path.join(__dirname, "test.crt"), "utf8" certificatePassword: "test" diff --git a/test/fixtures/getCustomerAccounts.json b/test/fixtures/getCustomerAccounts.json new file mode 100644 index 0000000..405125a --- /dev/null +++ b/test/fixtures/getCustomerAccounts.json @@ -0,0 +1,55 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts", + "body": "", + "status": 200, + "response": { + "accounts": [ + { + "type": "bankingAccount", + "bankingAccountType": "CHECKING", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438066800000, + "lastTxnDate": 1437894000000, + "accountId": 400107846787, + "accountNumber": "1000001111", + "accountNickname": "My Checking", + "displayPosition": 6, + "institutionId": 100000, + "balanceAmount": 728.05, + "aggrSuccessDate": 1438131959943, + "aggrAttemptDate": 1438131959943, + "aggrStatusCode": "0", + "institutionLoginId": 1275950117 + }, + { + "type": "bankingAccount", + "bankingAccountType": "SAVINGS", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438066800000, + "lastTxnDate": 1437894000000, + "accountId": 400107846795, + "accountNumber": "1000002222", + "accountNickname": "My Savings", + "displayPosition": 7, + "institutionId": 100000, + "balanceAmount": 728.05, + "aggrSuccessDate": 1438131959943, + "aggrAttemptDate": 1438131959943, + "aggrStatusCode": "0", + "institutionLoginId": 1275950117 + } + ]}, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index f7aba27..dc5112b 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -14,6 +14,21 @@ oauth = -> fixtures = oauth: oauth + + # Fail the test if these headers aren't generated + signedJson: -> + oauth() + nock("https://financialdatafeed.platform.intuit.com:443", { + reqheaders: + "Content-Type": "application/json" + "Accept": "application/json" + }) + .get("/v1/institutions/100000") + .matchHeader("Authorization", (header) -> + /oauth_signature/.test header + ) + .reply(200, {}) + getInstitutionDetails: -> oauth() load "getInstitutionDetails" From a9ff005c8edf75269728ec3dc38882712737a212 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 21:56:44 -0700 Subject: [PATCH 07/24] Move keys to fixtures dir --- test/config.coffee | 4 ++-- test/{ => fixtures}/test.crt | 0 test/{ => fixtures}/test.key | 0 test/{ => fixtures}/test.pfx | Bin 4 files changed, 2 insertions(+), 2 deletions(-) rename test/{ => fixtures}/test.crt (100%) rename test/{ => fixtures}/test.key (100%) rename test/{ => fixtures}/test.pfx (100%) diff --git a/test/config.coffee b/test/config.coffee index ada9d5c..d1c96df 100644 --- a/test/config.coffee +++ b/test/config.coffee @@ -5,6 +5,6 @@ module.exports = issuerId: "issuer-id" consumerKey: "consumer-key" consumerSecret: "consumer-secret" - key: fs.readFileSync path.join(__dirname, "test.key"), "utf8" - certificate: fs.readFileSync path.join(__dirname, "test.crt"), "utf8" + key: fs.readFileSync path.join(__dirname, "./fixtures/test.key"), "utf8" + certificate: fs.readFileSync path.join(__dirname, "./fixtures/test.crt"), "utf8" certificatePassword: "test" diff --git a/test/test.crt b/test/fixtures/test.crt similarity index 100% rename from test/test.crt rename to test/fixtures/test.crt diff --git a/test/test.key b/test/fixtures/test.key similarity index 100% rename from test/test.key rename to test/fixtures/test.key diff --git a/test/test.pfx b/test/fixtures/test.pfx similarity index 100% rename from test/test.pfx rename to test/fixtures/test.pfx From 293d71ef797650cf2a032ae8f4ea8fac4cc1b02e Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 22:47:21 -0700 Subject: [PATCH 08/24] Institutions test and cleanup --- test/api.coffee | 49 +++------- test/fixtures/index.coffee | 3 + test/fixtures/institutions.json | 166 ++++++++++++++++++++++++++++++++ test/lib/request.coffee | 23 +++++ 4 files changed, 206 insertions(+), 35 deletions(-) create mode 100644 test/fixtures/institutions.json create mode 100644 test/lib/request.coffee diff --git a/test/api.coffee b/test/api.coffee index 88e1721..2662a24 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -8,39 +8,21 @@ intuit = intuitClient config fixture = require "./fixtures" describe "Intuit Client", -> - describe "Data", -> - describe "headers", -> - before -> - fixture.load "signedJson" - before (done) -> - intuit.getInstitutionDetails "userId", 100000, (@err) => - done null - - it "should make a signed request for JSON", -> - assert.equal @err, null - describe "API", -> describe "institutions", -> - it "should be defined", -> - assert.notEqual intuit.institutions, undefined + before -> fixture.load "institutions" + it "should return institutions", (done) -> + intuit.institutions "userId", (err, institutions) => + assert.equal institutions.length, 20 + done err describe "getInstitutionDetails", -> - before -> - fixture.load "getInstitutionDetails" - @spy = bond(request, "get").through() - before (done) -> - intuit.getInstitutionDetails "userId", 100000, (err, @institutionDetails) => + before -> fixture.load "getInstitutionDetails" + it "should return an institution's details", (done) -> + intuit.getInstitutionDetails "userId", 100000, (err, institutionDetails) -> + assert.equal institutionDetails.institutionName, "CCBank-Beacon" done err - it "should use OAuth data to sign request", -> - assert @spy.calledArgs[0][0].oauth.consumer_key - assert @spy.calledArgs[0][0].oauth.consumer_secret - assert @spy.calledArgs[0][0].oauth.token - assert @spy.calledArgs[0][0].oauth.token_secret - - it "should return institution details", -> - assert.equal @institutionDetails.institutionName, "CCBank-Beacon" - describe.skip "discoverAndAddAccounts", -> it "should return newly created accounts", -> assert.notEqual intuit.discoverAndAddAccounts, undefined @@ -50,16 +32,13 @@ describe "Intuit Client", -> assert.notEqual intuit.mfa, undefined describe "getCustomerAccounts", -> - before -> - fixture.load "oauth" - fixture.load "getCustomerAccounts" - before (done) -> - intuit.getCustomerAccounts "todd", (err, @accounts) => + before -> fixture.load "oauth" + before -> fixture.load "getCustomerAccounts" + it "should return all accounts", (done) -> + intuit.getCustomerAccounts "todd", (err, accounts) => + assert.equal accounts.length, 2 done err - it "should return all accounts", -> - assert.equal @accounts.length, 2 - describe.skip "getAccount", -> it "should return an account", -> assert.notEqual intuit.getAccount, undefined diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index dc5112b..333fde1 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -33,6 +33,9 @@ fixtures = oauth() load "getInstitutionDetails" + institutions: -> + oauth() + load "institutions" module.exports = record: -> diff --git a/test/fixtures/institutions.json b/test/fixtures/institutions.json new file mode 100644 index 0000000..e852b59 --- /dev/null +++ b/test/fixtures/institutions.json @@ -0,0 +1,166 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/institutions", + "body": "", + "status": 200, + "response": { + "institution": [ + { + "homeUrl": "http:\/\/www.cffcu.org\/index.html", + "phoneNumber": "1-864-585-6838", + "institutionId": 8860, + "virtual": false, + "institutionName": "Carolina Foothills FCU Credit Card" + }, + { + "homeUrl": "https:\/\/www.christianfinancialcu.com\/", + "phoneNumber": "586-772-6330", + "institutionId": 8844, + "virtual": false, + "institutionName": "Christian Financial Credit Union - Credit Card" + }, + { + "homeUrl": "http:\/\/www.myacfcu.com\/ASP\/home.asp", + "phoneNumber": "1-800-378-3778", + "institutionId": 8851, + "virtual": false, + "institutionName": "Appalachian Community FCU Credit Card" + }, + { + "homeUrl": "http:\/\/www.southside.com\/index.htm", + "phoneNumber": "1-903-531-7111", + "institutionId": 8819, + "virtual": false, + "institutionName": "Southside Bank (TX) Credit Card" + }, + { + "homeUrl": "http:\/\/www.gasfcu.org\/", + "phoneNumber": "1-800-844-5363", + "institutionId": 8886, + "virtual": false, + "institutionName": "Glendale Area Schools FCU Credit Card" + }, + { + "homeUrl": "https:\/\/www.southernfirst.com\/", + "phoneNumber": "864-679-9000", + "institutionId": 8890, + "virtual": false, + "institutionName": "Southern First Bank (SC) - Business Banking" + }, + { + "homeUrl": "http:\/\/www.htfffcu.org\/", + "phoneNumber": "1-713-864-0959", + "institutionId": 8891, + "virtual": false, + "institutionName": "Houston TX Firefighters FCU Credit Card" + }, + { + "homeUrl": "https:\/\/www.mckaydeecu.com\/index.asp", + "phoneNumber": "1-801-476-8000", + "institutionId": 8900, + "virtual": false, + "institutionName": "SummitOne Credit Union" + }, + { + "homeUrl": "http:\/\/www.primefinancialcu.org\/default.aspx", + "phoneNumber": "1-800-835-9680", + "institutionId": 8905, + "virtual": false, + "institutionName": "Prime Financial CU Credit Card" + }, + { + "homeUrl": "http:\/\/www.andrewsfcu.org\/", + "phoneNumber": "301-702-5500", + "institutionId": 8960, + "virtual": false, + "institutionName": "Andrews Federal Credit Union - Credit Card" + }, + { + "homeUrl": "http:\/\/www.associatedbank.com", + "phoneNumber": "1-800-682-4989", + "institutionId": 8965, + "virtual": false, + "institutionName": "Associated Bank Credit Card" + }, + { + "homeUrl": "http:\/\/www.veritasfcu.org\/", + "phoneNumber": "615-786-0452", + "institutionId": 8910, + "virtual": false, + "institutionName": "Veritas Federal Credit Union (TN) - Credit Card" + }, + { + "homeUrl": "http:\/\/www.omegafcu.com\/", + "phoneNumber": "1-412-369-3800", + "institutionId": 8912, + "virtual": false, + "institutionName": "Omega FCU Credit Card" + }, + { + "homeUrl": "http:\/\/www.solfcu.org\/ASP\/home.asp", + "phoneNumber": "1-800-999-5894", + "institutionId": 8929, + "virtual": false, + "institutionName": "Solidarity Community FCU Credit Card" + }, + { + "homeUrl": "http:\/\/www.mmfcu.org\/", + "phoneNumber": "1-218-829-0371", + "institutionId": 8949, + "virtual": false, + "institutionName": "Mid-Minnesota FCU Credit Card" + }, + { + "homeUrl": "http:\/\/www.moneyonefcu.org\/", + "phoneNumber": "1-800-638-0232", + "institutionId": 8951, + "virtual": false, + "institutionName": "Money One FCU Credit Card" + }, + { + "homeUrl": "http:\/\/www.genfed.com", + "phoneNumber": "1-888-443-6333", + "institutionId": 8993, + "virtual": false, + "institutionName": "GenFed FCU Credit Card" + }, + { + "homeUrl": "http:\/\/www.stellarone.com\/", + "phoneNumber": "540-382-4951", + "institutionId": 9008, + "virtual": false, + "institutionName": "StellarOne (VA) - Business Banking" + }, + { + "homeUrl": "https:\/\/www.oucu.org\/", + "phoneNumber": "1-800-562-8420", + "institutionId": 9016, + "virtual": false, + "institutionName": "Ohio University Credit Union" + }, + { + "homeUrl": "https:\/\/www.thefriendlybank.com\/", + "phoneNumber": "225-638-3713 ", + "institutionId": 9017, + "virtual": false, + "institutionName": "Peoples Bank and Trust (LA)" + } + ] + }, + "headers": { + "date": "Tue, 28 Jul 2015 22:39:38 GMT", + "pragma": "no-cache", + "content-type": "application/json", + "content-length": "670", + "cache-control": "no-cache", + "expires": "Wed, 31 Dec 1969 16:00:00 PST", + "intuit_tid": "gw-8937219d-6a7f-4543-bd11-f52eb63a09c6", + "set-cookie": [ + "JSESSIONID=80381852D0F879D03D6E539E65F273E4.nodepprdccoap40w.corp.intuit.net; Path=/rest-war" + ], + "connection": "close" + } + } +] diff --git a/test/lib/request.coffee b/test/lib/request.coffee new file mode 100644 index 0000000..95bd736 --- /dev/null +++ b/test/lib/request.coffee @@ -0,0 +1,23 @@ +assert = require "assert" +bond = require "bondjs" +request = require "request" +OAuth = require "../../lib/oauth" +config = require "../config" +fixture = require "../fixtures" +intuit = require("../../")(config) + +describe "JSON Authentication", -> + describe "Request", -> + before -> @spy = bond(request, "get").through() + before -> fixture.load "signedJson" + before (done) -> + intuit.getInstitutionDetails "userId", 100000, (@err) => + done null + + it "should use OAuth data to sign request", -> + assert @spy.calledArgs[0][0].oauth.consumer_key + assert @spy.calledArgs[0][0].oauth.consumer_secret + assert @spy.calledArgs[0][0].oauth.token + assert @spy.calledArgs[0][0].oauth.token_secret + it "should make a signed request for JSON without error", -> + assert.equal @err, null From 59b2ace6bd64df4856ba9b195e2a1d301619e4ba Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Tue, 28 Jul 2015 23:52:47 -0700 Subject: [PATCH 09/24] Add tests for more GET requests --- lib/client.coffee | 5 +++- lib/request.coffee | 4 ++- test/api.coffee | 46 ++++++++++++++++---------------- test/fixtures/deleteAccount.json | 23 ++++++++++++++++ test/fixtures/getAccount.json | 37 +++++++++++++++++++++++++ test/fixtures/index.coffee | 12 +++++++++ 6 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/deleteAccount.json create mode 100644 test/fixtures/getAccount.json diff --git a/lib/client.coffee b/lib/client.coffee index 2d3a5a7..04fbbc2 100644 --- a/lib/client.coffee +++ b/lib/client.coffee @@ -1,3 +1,4 @@ +_ = require "lodash" Request = require "./request" BASE_URL = "https://financialdatafeed.platform.intuit.com/v1" @@ -31,7 +32,9 @@ module.exports = class IntuitClient getAccount: (userId, accountId, done) -> @options.userId = userId - @request "get", "/accounts/#{accountId}", done + @request "get", "/accounts/#{accountId}", (err, response) -> + return done err if err + done err, _.first response.accounts getAccountTransactions: (userId, accountId, startDate, endDate) -> @options.userId = userId diff --git a/lib/request.coffee b/lib/request.coffee index 9bc3ca4..3b49278 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -24,9 +24,11 @@ module.exports = class Request done err, params request: (params, done) -> - requestMethod = request[params.method.toLowerCase()] + method = if params.method is "DELETE" then params.method.toLowerCase()[0..2] else params.method.toLowerCase() + requestMethod = request[method] requestMethod params, (err, response, body) -> debug "#{response.statusCode} - #{response.statusMessage} - #{response.request.uri.path}" + return done null, response.statusCode if response.statusCode is 200 and not body try parsed = JSON.parse body catch e diff --git a/test/api.coffee b/test/api.coffee index 2662a24..4233d4d 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -23,38 +23,38 @@ describe "Intuit Client", -> assert.equal institutionDetails.institutionName, "CCBank-Beacon" done err - describe.skip "discoverAndAddAccounts", -> - it "should return newly created accounts", -> - assert.notEqual intuit.discoverAndAddAccounts, undefined + describe "discoverAndAddAccounts", -> + it "should return newly created accounts" - describe.skip "mfa", -> - it "should handle MFA", -> - assert.notEqual intuit.mfa, undefined + describe "mfa", -> + it "should handle MFA" describe "getCustomerAccounts", -> - before -> fixture.load "oauth" before -> fixture.load "getCustomerAccounts" it "should return all accounts", (done) -> - intuit.getCustomerAccounts "todd", (err, accounts) => + intuit.getCustomerAccounts "userId", (err, accounts) -> assert.equal accounts.length, 2 done err - describe.skip "getAccount", -> - it "should return an account", -> - assert.notEqual intuit.getAccount, undefined + describe "getAccount", -> + before -> fixture.load "getAccount" + it "should return an account", (done) -> + intuit.getAccount "userId", 400107846787, (err, account) -> + assert.equal account.bankingAccountType, "CHECKING" + done err - describe.skip "getAccountTransactions", -> - it "should return transactions", -> - assert.notEqual intuit.getAccountTransactions, undefined + describe "getAccountTransactions", -> + it "should return transactions" - describe.skip "updateInstitutionLogin", -> - it "should update bank credentials", -> - assert.notEqual intuit.updateInstitutionLogin, undefined + describe "updateInstitutionLogin", -> + it "should update bank credentials" - describe.skip "deleteAccount", -> - it "should remove the account", -> - assert.notEqual intuit.deleteAccount, undefined + describe "deleteAccount", -> + before -> fixture.load "deleteAccount" + it "should remove the account", (done) -> + intuit.deleteAccount "userId", 400107846793, (err, response) -> + assert.equal response, 200 + done err - describe.skip "deleteCustomer", -> - it "should remove the user", -> - assert.notEqual intuit.deleteCustomer, undefined + describe "deleteCustomer", -> + it "should remove the user" diff --git a/test/fixtures/deleteAccount.json b/test/fixtures/deleteAccount.json new file mode 100644 index 0000000..bba302b --- /dev/null +++ b/test/fixtures/deleteAccount.json @@ -0,0 +1,23 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "DELETE", + "path": "/v1/accounts/400107846793", + "body": "", + "status": 200, + "response": "", + "headers": { + "date": "Tue, 28 Jul 2015 22:39:38 GMT", + "pragma": "no-cache", + "content-type": "application/json", + "content-length": "670", + "cache-control": "no-cache", + "expires": "Wed, 31 Dec 1969 16:00:00 PST", + "intuit_tid": "gw-8937219d-6a7f-4543-bd11-f52eb63a09c6", + "set-cookie": [ + "JSESSIONID=80381852D0F879D03D6E539E65F273E4.nodepprdccoap40w.corp.intuit.net; Path=/rest-war" + ], + "connection": "close" + } + } +] \ No newline at end of file diff --git a/test/fixtures/getAccount.json b/test/fixtures/getAccount.json new file mode 100644 index 0000000..cee0b34 --- /dev/null +++ b/test/fixtures/getAccount.json @@ -0,0 +1,37 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts/400107846787", + "body": "", + "status": 200, + "response": { + "accounts": [ + { + "type": "bankingAccount", + "bankingAccountType": "CHECKING", + "accountId": 400107846787, + "currencyCode": "BND", + "balanceDate": 1438066800000, + "lastTxnDate": 1437894000000, + "status": "ACTIVE", + "accountNumber": "1000001111", + "accountNickname": "My Checking", + "displayPosition": 6, + "institutionId": 100000, + "balanceAmount": 728.5, + "aggrSuccessDate": 1438149031166, + "aggrAttemptDate": 1438149031166, + "aggrStatusCode": "0", + "institutionLoginId": 1275950117 + } + ]}, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 333fde1..2b96883 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -37,6 +37,18 @@ fixtures = oauth() load "institutions" + deleteAccount: -> + oauth() + load "deleteAccount" + + getCustomerAccounts: -> + oauth() + load "getCustomerAccounts" + + getAccount: -> + oauth() + load "getAccount" + module.exports = record: -> nock.recorder.rec From 8a6f9bc4b50b6033e56176cba3fb8d38f8cf820c Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Wed, 29 Jul 2015 09:08:29 -0700 Subject: [PATCH 10/24] Move header assertion to nock to fix test --- lib/oauth.coffee | 2 +- test/fixtures/index.coffee | 11 +++++++++++ test/lib/oauth.coffee | 8 ++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/oauth.coffee b/lib/oauth.coffee index e35f929..d7030ae 100644 --- a/lib/oauth.coffee +++ b/lib/oauth.coffee @@ -22,7 +22,7 @@ module.exports = class OAuth "Authorization": "OAuth oauth_consumer_key=\"#{@options.consumerKey}\"" form: saml_assertion: @saml - request.post params, (err, response, body) => + request params, (err, response, body) => return done err if err oauth = @parseResponse body done err, oauth.token, oauth.tokenSecret diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 2b96883..48d87c2 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -1,5 +1,6 @@ _ = require "lodash" nock = require "nock" +config = require "../config" path = require "path" fs = require "fs" @@ -29,6 +30,16 @@ fixtures = ) .reply(200, {}) + signedSaml: -> + nock("https://oauth.intuit.com:443") + .filteringRequestBody((body) -> return "SAML") + .post("/oauth/v1/get_access_token_by_saml", "SAML") + .matchHeader("Authorization", (header) -> + regex = new RegExp "#{config.consumerKey}" + regex.test header + ) + .reply(200, "oauth_token_secret=L63cI4q5UhP4mQzpCi1RHBMSLe2TNOaI98vxyIBL&oauth_token=qyprdcvDh5V2XoJRwYmxxdL5vOJ54Z6sNVohlNLQHyyhHaAy") + getInstitutionDetails: -> oauth() load "getInstitutionDetails" diff --git a/test/lib/oauth.coffee b/test/lib/oauth.coffee index 1051ee2..580d273 100644 --- a/test/lib/oauth.coffee +++ b/test/lib/oauth.coffee @@ -8,22 +8,18 @@ fixture = require "../fixtures" describe "OAuth", -> describe "getToken", -> before -> @spy = bond(request, "post").through() - before -> fixture.load "oauth", 3 + before -> fixture.load "signedSaml" before (done) -> oauth = new OAuth config oauth.getToken (@err, @token, @secret) => done @err after -> @spy.restore() - it "should return an OAuth token", -> + it "should make an Authorized request to return an OAuth token without error", -> assert.equal @err, null assert @token assert @secret - it "should have an Authorization header with the consumer key", -> - regex = new RegExp "#{config.consumerKey}" - assert regex.test @spy.calledArgs[0][0].headers.Authorization - describe "parseResponse", -> it "should parse the response for a token and a secret", -> oauth = new OAuth config From 9fdadab26abeafccb7c55666127460544b5d47ea Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Wed, 29 Jul 2015 10:00:14 -0700 Subject: [PATCH 11/24] Clean up fixtures --- test/fixtures/index.coffee | 34 +++++++++++++++------------------- test/lib/oauth.coffee | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 48d87c2..101daab 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -11,35 +11,31 @@ oauth = -> nock("https://oauth.intuit.com:443") .filteringRequestBody((body) -> return "SAML") .post("/oauth/v1/get_access_token_by_saml", "SAML") + .matchHeader("Authorization", authorizationHeader) .reply(200, "oauth_token_secret=L63cI4q5UhP4mQzpCi1RHBMSLe2TNOaI98vxyIBL&oauth_token=qyprdcvDh5V2XoJRwYmxxdL5vOJ54Z6sNVohlNLQHyyhHaAy") +jsonHeader = (header) -> + header is "application/json" + +signedHeader = (header) -> + /oauth_signature/.test header + +authorizationHeader = (header) -> + regex = new RegExp "#{config.consumerKey}" + regex.test header + fixtures = oauth: oauth - # Fail the test if these headers aren't generated signedJson: -> oauth() - nock("https://financialdatafeed.platform.intuit.com:443", { - reqheaders: - "Content-Type": "application/json" - "Accept": "application/json" - }) + nock("https://financialdatafeed.platform.intuit.com:443") .get("/v1/institutions/100000") - .matchHeader("Authorization", (header) -> - /oauth_signature/.test header - ) + .matchHeader("Content-Type", jsonHeader) + .matchHeader("Accept", jsonHeader) + .matchHeader("Authorization", signedHeader) .reply(200, {}) - signedSaml: -> - nock("https://oauth.intuit.com:443") - .filteringRequestBody((body) -> return "SAML") - .post("/oauth/v1/get_access_token_by_saml", "SAML") - .matchHeader("Authorization", (header) -> - regex = new RegExp "#{config.consumerKey}" - regex.test header - ) - .reply(200, "oauth_token_secret=L63cI4q5UhP4mQzpCi1RHBMSLe2TNOaI98vxyIBL&oauth_token=qyprdcvDh5V2XoJRwYmxxdL5vOJ54Z6sNVohlNLQHyyhHaAy") - getInstitutionDetails: -> oauth() load "getInstitutionDetails" diff --git a/test/lib/oauth.coffee b/test/lib/oauth.coffee index 580d273..138ea8f 100644 --- a/test/lib/oauth.coffee +++ b/test/lib/oauth.coffee @@ -8,7 +8,7 @@ fixture = require "../fixtures" describe "OAuth", -> describe "getToken", -> before -> @spy = bond(request, "post").through() - before -> fixture.load "signedSaml" + before -> fixture.load "oauth" before (done) -> oauth = new OAuth config oauth.getToken (@err, @token, @secret) => From 8e58479e45610b62ec539e058c8150b55e43a260 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Wed, 29 Jul 2015 16:04:47 -0700 Subject: [PATCH 12/24] Add method and tests for getAccountTransactions with date range support --- lib/client.coffee | 31 +++- package.json | 3 +- test/api.coffee | 28 ++- test/fixtures/getAccountTransactions.json | 159 ++++++++++++++++++ .../getAccountTransactions_dateRange.json | 159 ++++++++++++++++++ .../getAccountTransactions_startDate.json | 159 ++++++++++++++++++ test/fixtures/index.coffee | 12 ++ 7 files changed, 547 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/getAccountTransactions.json create mode 100644 test/fixtures/getAccountTransactions_dateRange.json create mode 100644 test/fixtures/getAccountTransactions_startDate.json diff --git a/lib/client.coffee b/lib/client.coffee index 04fbbc2..c06fd3f 100644 --- a/lib/client.coffee +++ b/lib/client.coffee @@ -3,6 +3,16 @@ Request = require "./request" BASE_URL = "https://financialdatafeed.platform.intuit.com/v1" +zeroBased = (number) -> + ("0" + number).slice(-2) + +# 2015-07-29 +formatDate = (date) -> + year = date.getFullYear() + month = zeroBased date.getMonth() + 1 + day = zeroBased date.getDate() + "#{year}-#{month}-#{day}" + module.exports = class IntuitClient constructor: (@options) -> @@ -36,9 +46,26 @@ module.exports = class IntuitClient return done err if err done err, _.first response.accounts - getAccountTransactions: (userId, accountId, startDate, endDate) -> + # Download last week of transactions unless a date range is specified + # If no endDate is specified, startDate to current is downloaded + getAccountTransactions: (userId, accountId, startDate, endDate, done) -> + oneWeekAgo = -10080 + defaultStartDate = formatDate new Date(new Date().getTime() + oneWeekAgo * 60000) + + if typeof(startDate) is "function" + [startDate, endDate, done] = [defaultStartDate, null, startDate] + else if typeof(endDate) is "function" + [startDate, endDate, done] = [formatDate(startDate), null, endDate] + else + [startDate, endDate, done] = [formatDate(startDate), formatDate(endDate), done] + + url = "/accounts/#{accountId}/transactions?txnStartDate=#{startDate}" + url += "&txnEndDate=#{endDate}" if endDate + @options.userId = userId - @request "get", "/accounts/#{accountId}/transactions", done + @request "get", url, (err, response) -> + return done err if err + done err, response.bankingTransactions updateInstitutionLogin: (userId, institutionId, loginId, credentials, done) -> @options.userId = userId diff --git a/package.json b/package.json index afd3796..d26fa73 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "devDependencies": { "bondjs": "^1.2.3", "mocha": "^2.2.5", - "nock": "^2.9.1" + "nock": "^2.9.1", + "timekeeper": "0.0.5" }, "engines": { "node": "0.12.5", diff --git a/test/api.coffee b/test/api.coffee index 4233d4d..6f86317 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -1,4 +1,5 @@ assert = require "assert" +timekeeper = require "timekeeper" request = require "request" bond = require "bondjs" request = require "request" @@ -44,7 +45,32 @@ describe "Intuit Client", -> done err describe "getAccountTransactions", -> - it "should return transactions" + describe "default parameters", -> + before -> timekeeper.freeze new Date "2015-07-30" + before -> fixture.load "getAccountTransactions" + after -> timekeeper.reset() + it "should return transactions", (done) -> + intuit.getAccountTransactions "userId", 400107846787, (err, transactions) -> + assert.equal transactions.length, 8 + done err + + describe "Specific start date", -> + before -> timekeeper.freeze new Date "2015-07-30" + before -> fixture.load "getAccountTransactions_startDate" + after -> timekeeper.reset() + it "should return transactions", (done) -> + intuit.getAccountTransactions "userId", 400107846787, new Date("2015-05-15"), (err, transactions) -> + assert.equal transactions.length, 8 + done err + + describe "Specific date range", -> + before -> timekeeper.freeze new Date "2015-07-30" + before -> fixture.load "getAccountTransactions_dateRange" + after -> timekeeper.reset() + it "should return transactions", (done) -> + intuit.getAccountTransactions "userId", 400107846787, new Date("2015-05-15"), new Date("2015-05-22"), (err, transactions) -> + assert.equal transactions.length, 8 + done err describe "updateInstitutionLogin", -> it "should update bank credentials" diff --git a/test/fixtures/getAccountTransactions.json b/test/fixtures/getAccountTransactions.json new file mode 100644 index 0000000..095a57f --- /dev/null +++ b/test/fixtures/getAccountTransactions.json @@ -0,0 +1,159 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-07-22", + "body": "", + "status": 200, + "response": { + "bankingTransactions": [ + { + "id": 403657885582, + "currencyType": "USD", + "postedDate": 1437634800000, + "payeeName": "CHECKINGD debit 204", + "amount": -7.23, + "pending": false, + "institutionTransactionId": "INTUIT-403657885582", + "userDate": 1437634800000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885585, + "currencyType": "USD", + "postedDate": 1437894000000, + "payeeName": "CHECKINGD credit 207", + "amount": 7.26, + "pending": false, + "institutionTransactionId": "INTUIT-403657885585", + "userDate": 1437894000000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885595, + "currencyType": "USD", + "postedDate": 1437548400000, + "payeeName": "CHECKINGD credit 203", + "amount": 7.22, + "pending": false, + "institutionTransactionId": "INTUIT-403657885595", + "userDate": 1437548400000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885902, + "currencyType": "USD", + "postedDate": 1437807600000, + "payeeName": "CHECKINGD debit 206", + "amount": -7.25, + "pending": false, + "institutionTransactionId": "INTUIT-403657885902", + "userDate": 1437807600000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885905, + "currencyType": "USD", + "postedDate": 1437721200000, + "payeeName": "CHECKINGD credit 205", + "amount": 7.24, + "pending": false, + "institutionTransactionId": "INTUIT-403657885905", + "userDate": 1437721200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403663363252, + "currencyType": "USD", + "postedDate": 1437980400000, + "payeeName": "CHECKINGD debit 208", + "amount": -7.27, + "pending": false, + "institutionTransactionId": "INTUIT-403663363252", + "userDate": 1437980400000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403664091017, + "currencyType": "USD", + "payeeName": "CHECKINGD credit 209", + "amount": 7.28, + "pending": true, + "userDate": 1438153200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403664091018, + "currencyType": "USD", + "payeeName": "CHECKINGD debit 210", + "amount": -7.29, + "pending": true, + "userDate": 1438153200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + } + ]}, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/getAccountTransactions_dateRange.json b/test/fixtures/getAccountTransactions_dateRange.json new file mode 100644 index 0000000..81bec2c --- /dev/null +++ b/test/fixtures/getAccountTransactions_dateRange.json @@ -0,0 +1,159 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-05-14&txnEndDate=2015-05-21", + "body": "", + "status": 200, + "response": { + "bankingTransactions": [ + { + "id": 403657885582, + "currencyType": "USD", + "postedDate": 1437634800000, + "payeeName": "CHECKINGD debit 204", + "amount": -7.23, + "pending": false, + "institutionTransactionId": "INTUIT-403657885582", + "userDate": 1437634800000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885585, + "currencyType": "USD", + "postedDate": 1437894000000, + "payeeName": "CHECKINGD credit 207", + "amount": 7.26, + "pending": false, + "institutionTransactionId": "INTUIT-403657885585", + "userDate": 1437894000000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885595, + "currencyType": "USD", + "postedDate": 1437548400000, + "payeeName": "CHECKINGD credit 203", + "amount": 7.22, + "pending": false, + "institutionTransactionId": "INTUIT-403657885595", + "userDate": 1437548400000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885902, + "currencyType": "USD", + "postedDate": 1437807600000, + "payeeName": "CHECKINGD debit 206", + "amount": -7.25, + "pending": false, + "institutionTransactionId": "INTUIT-403657885902", + "userDate": 1437807600000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885905, + "currencyType": "USD", + "postedDate": 1437721200000, + "payeeName": "CHECKINGD credit 205", + "amount": 7.24, + "pending": false, + "institutionTransactionId": "INTUIT-403657885905", + "userDate": 1437721200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403663363252, + "currencyType": "USD", + "postedDate": 1437980400000, + "payeeName": "CHECKINGD debit 208", + "amount": -7.27, + "pending": false, + "institutionTransactionId": "INTUIT-403663363252", + "userDate": 1437980400000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403664091017, + "currencyType": "USD", + "payeeName": "CHECKINGD credit 209", + "amount": 7.28, + "pending": true, + "userDate": 1438153200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403664091018, + "currencyType": "USD", + "payeeName": "CHECKINGD debit 210", + "amount": -7.29, + "pending": true, + "userDate": 1438153200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + } + ]}, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/getAccountTransactions_startDate.json b/test/fixtures/getAccountTransactions_startDate.json new file mode 100644 index 0000000..043b0ce --- /dev/null +++ b/test/fixtures/getAccountTransactions_startDate.json @@ -0,0 +1,159 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-05-14", + "body": "", + "status": 200, + "response": { + "bankingTransactions": [ + { + "id": 403657885582, + "currencyType": "USD", + "postedDate": 1437634800000, + "payeeName": "CHECKINGD debit 204", + "amount": -7.23, + "pending": false, + "institutionTransactionId": "INTUIT-403657885582", + "userDate": 1437634800000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885585, + "currencyType": "USD", + "postedDate": 1437894000000, + "payeeName": "CHECKINGD credit 207", + "amount": 7.26, + "pending": false, + "institutionTransactionId": "INTUIT-403657885585", + "userDate": 1437894000000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885595, + "currencyType": "USD", + "postedDate": 1437548400000, + "payeeName": "CHECKINGD credit 203", + "amount": 7.22, + "pending": false, + "institutionTransactionId": "INTUIT-403657885595", + "userDate": 1437548400000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885902, + "currencyType": "USD", + "postedDate": 1437807600000, + "payeeName": "CHECKINGD debit 206", + "amount": -7.25, + "pending": false, + "institutionTransactionId": "INTUIT-403657885902", + "userDate": 1437807600000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403657885905, + "currencyType": "USD", + "postedDate": 1437721200000, + "payeeName": "CHECKINGD credit 205", + "amount": 7.24, + "pending": false, + "institutionTransactionId": "INTUIT-403657885905", + "userDate": 1437721200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403663363252, + "currencyType": "USD", + "postedDate": 1437980400000, + "payeeName": "CHECKINGD debit 208", + "amount": -7.27, + "pending": false, + "institutionTransactionId": "INTUIT-403663363252", + "userDate": 1437980400000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403664091017, + "currencyType": "USD", + "payeeName": "CHECKINGD credit 209", + "amount": 7.28, + "pending": true, + "userDate": 1438153200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + }, + { + "id": 403664091018, + "currencyType": "USD", + "payeeName": "CHECKINGD debit 210", + "amount": -7.29, + "pending": true, + "userDate": 1438153200000, + "categorization": { + "context": [ + + ], + "common": { + + } + } + } + ]}, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 101daab..a1ec632 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -56,6 +56,18 @@ fixtures = oauth() load "getAccount" + getAccountTransactions: -> + oauth() + load "getAccountTransactions" + + getAccountTransactions_startDate: -> + oauth() + load "getAccountTransactions_startDate" + + getAccountTransactions_dateRange: -> + oauth() + load "getAccountTransactions_dateRange" + module.exports = record: -> nock.recorder.rec From 1e173203a285a0ffe74f571859c33f67ea0ea498 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Wed, 29 Jul 2015 16:11:01 -0700 Subject: [PATCH 13/24] Add deleteCustomer method and test --- test/api.coffee | 6 +++++- test/fixtures/deleteCustomer.json | 23 +++++++++++++++++++++++ test/fixtures/index.coffee | 4 ++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/deleteCustomer.json diff --git a/test/api.coffee b/test/api.coffee index 6f86317..521f26a 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -83,4 +83,8 @@ describe "Intuit Client", -> done err describe "deleteCustomer", -> - it "should remove the user" + before -> fixture.load "deleteCustomer" + it "should remove the user", (done) -> + intuit.deleteCustomer "userId", (err, response) -> + assert.equal response, 200 + done err diff --git a/test/fixtures/deleteCustomer.json b/test/fixtures/deleteCustomer.json new file mode 100644 index 0000000..11efac5 --- /dev/null +++ b/test/fixtures/deleteCustomer.json @@ -0,0 +1,23 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "DELETE", + "path": "/v1/customers", + "body": "", + "status": 200, + "response": "", + "headers": { + "date": "Tue, 28 Jul 2015 22:39:38 GMT", + "pragma": "no-cache", + "content-type": "application/json", + "content-length": "670", + "cache-control": "no-cache", + "expires": "Wed, 31 Dec 1969 16:00:00 PST", + "intuit_tid": "gw-8937219d-6a7f-4543-bd11-f52eb63a09c6", + "set-cookie": [ + "JSESSIONID=80381852D0F879D03D6E539E65F273E4.nodepprdccoap40w.corp.intuit.net; Path=/rest-war" + ], + "connection": "close" + } + } +] \ No newline at end of file diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index a1ec632..6167d84 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -68,6 +68,10 @@ fixtures = oauth() load "getAccountTransactions_dateRange" + deleteCustomer: -> + oauth() + load "deleteCustomer" + module.exports = record: -> nock.recorder.rec From a52a60883943afee750acd28f943744aa2009d22 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Wed, 29 Jul 2015 16:25:55 -0700 Subject: [PATCH 14/24] Fix timezone for transaction tests --- test/api.coffee | 1 + test/fixtures/getAccountTransactions.json | 2 +- test/fixtures/getAccountTransactions_dateRange.json | 2 +- test/fixtures/getAccountTransactions_startDate.json | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/api.coffee b/test/api.coffee index 521f26a..d7fff52 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -7,6 +7,7 @@ intuitClient = require "../" config = require "./config" intuit = intuitClient config fixture = require "./fixtures" +process.env.TZ = "UTC" describe "Intuit Client", -> describe "API", -> diff --git a/test/fixtures/getAccountTransactions.json b/test/fixtures/getAccountTransactions.json index 095a57f..be74d91 100644 --- a/test/fixtures/getAccountTransactions.json +++ b/test/fixtures/getAccountTransactions.json @@ -2,7 +2,7 @@ { "scope": "https://financialdatafeed.platform.intuit.com:443", "method": "GET", - "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-07-22", + "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-07-23", "body": "", "status": 200, "response": { diff --git a/test/fixtures/getAccountTransactions_dateRange.json b/test/fixtures/getAccountTransactions_dateRange.json index 81bec2c..40fd552 100644 --- a/test/fixtures/getAccountTransactions_dateRange.json +++ b/test/fixtures/getAccountTransactions_dateRange.json @@ -2,7 +2,7 @@ { "scope": "https://financialdatafeed.platform.intuit.com:443", "method": "GET", - "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-05-14&txnEndDate=2015-05-21", + "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-05-15&txnEndDate=2015-05-22", "body": "", "status": 200, "response": { diff --git a/test/fixtures/getAccountTransactions_startDate.json b/test/fixtures/getAccountTransactions_startDate.json index 043b0ce..752268d 100644 --- a/test/fixtures/getAccountTransactions_startDate.json +++ b/test/fixtures/getAccountTransactions_startDate.json @@ -2,7 +2,7 @@ { "scope": "https://financialdatafeed.platform.intuit.com:443", "method": "GET", - "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-05-14", + "path": "/v1/accounts/400107846787/transactions?txnStartDate=2015-05-15", "body": "", "status": 200, "response": { From 142678cb6093d3f218ee695722f4219dac4fd7f0 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Wed, 29 Jul 2015 16:57:17 -0700 Subject: [PATCH 15/24] Add debugging for oauth call --- lib/oauth.coffee | 2 ++ lib/request.coffee | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/oauth.coffee b/lib/oauth.coffee index d7030ae..36e837c 100644 --- a/lib/oauth.coffee +++ b/lib/oauth.coffee @@ -1,3 +1,4 @@ +debug = require("debug")("node-intuit:oauth") request = require "request" Saml = require "./saml" @@ -24,5 +25,6 @@ module.exports = class OAuth saml_assertion: @saml request params, (err, response, body) => return done err if err + debug "#{params.method} #{response.request.uri.path} - #{response.statusCode} #{response.statusMessage}" oauth = @parseResponse body done err, oauth.token, oauth.tokenSecret diff --git a/lib/request.coffee b/lib/request.coffee index 3b49278..df4200f 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -27,7 +27,7 @@ module.exports = class Request method = if params.method is "DELETE" then params.method.toLowerCase()[0..2] else params.method.toLowerCase() requestMethod = request[method] requestMethod params, (err, response, body) -> - debug "#{response.statusCode} - #{response.statusMessage} - #{response.request.uri.path}" + debug "#{params.method} #{response.request.uri.path} - #{response.statusCode} #{response.statusMessage}" return done null, response.statusCode if response.statusCode is 200 and not body try parsed = JSON.parse body From 24423cce74528c545bd6a8da9becdebebc5cc90b Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 13:03:11 -0700 Subject: [PATCH 16/24] Fix POST/POST calls --- lib/request.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/request.coffee b/lib/request.coffee index df4200f..738b04c 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -44,13 +44,15 @@ module.exports = class Request post: (url, body, done) -> @_params "POST", url, (err, params) => return done err if err - params.form = body + params.body = body + params.json = true @request params, done put: (url, body, done) -> @_params "PUT", url, (err, params) => return done err if err - params.form = body + params.body = body + params.json = true @request params, done delete: (url, body, done) -> From 046276627b8cec27659e1975e3281e953dc57704 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 13:16:54 -0700 Subject: [PATCH 17/24] Add method and test for discoverAndAddAccounts --- lib/client.coffee | 4 +- test/api.coffee | 21 +- test/fixtures/discoverAndAddAccounts.json | 225 ++++++++++++++++++++++ test/fixtures/index.coffee | 4 + 4 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/discoverAndAddAccounts.json diff --git a/lib/client.coffee b/lib/client.coffee index c06fd3f..ee08f31 100644 --- a/lib/client.coffee +++ b/lib/client.coffee @@ -32,7 +32,9 @@ module.exports = class IntuitClient discoverAndAddAccounts: (userId, institutionId, credentials, done) -> @options.userId = userId - @request "post", "/institutions/#{institutionId}/logins", credentials, done + @request "post", "/institutions/#{institutionId}/logins", credentials, (err, response) -> + return done err if err + done err, response.accounts getCustomerAccounts: (userId, done) -> @options.userId = userId diff --git a/test/api.coffee b/test/api.coffee index d7fff52..f3c8d0b 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -26,7 +26,24 @@ describe "Intuit Client", -> done err describe "discoverAndAddAccounts", -> - it "should return newly created accounts" + before -> fixture.load "discoverAndAddAccounts" + it "should return newly created accounts", -> + loginDetails = + credentials: + credential: [ + { + name: "Banking Userid" + value: "demo" + } + + { + name: "Banking Password" + value: "go" + } + ] + intuit.discoverAndAddAccounts "userId", 100000, loginDetails, (err, accounts) -> + assert.equal accounts.length, 10 + done err describe "mfa", -> it "should handle MFA" @@ -42,7 +59,7 @@ describe "Intuit Client", -> before -> fixture.load "getAccount" it "should return an account", (done) -> intuit.getAccount "userId", 400107846787, (err, account) -> - assert.equal account.bankingAccountType, "CHECKING" + assert account.type done err describe "getAccountTransactions", -> diff --git a/test/fixtures/discoverAndAddAccounts.json b/test/fixtures/discoverAndAddAccounts.json new file mode 100644 index 0000000..46183cc --- /dev/null +++ b/test/fixtures/discoverAndAddAccounts.json @@ -0,0 +1,225 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts/400107846787", + "body": "", + "status": 200, + "response": { + "accounts": [ + { + "type": "creditAccount", + "creditAccountType": "LINEOFCREDIT", + "currentBalance": -730.09, + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072919, + "accountNumber": "8000006666", + "accountNickname": "My Line of Credit", + "displayPosition": 4, + "institutionId": 100000, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "TAXABLE", + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072920, + "accountNumber": "2000004444", + "accountNickname": "My Roth IRA", + "displayPosition": 3, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "loanAccount", + "loanType": "LOAN", + "firstPaymentDate": 1585724400000, + "guarantor": "Guarantor", + "collateral": "730.09", + "currentSchool": "Cur School", + "originalSchool": "Orig School", + "lender": "Lender", + "autopayEnrolled": true, + "paymentMinAmount": 730.09, + "recurringPaymentAmount": 730.09, + "firstMortgage": false, + "loanPaymentFreq": "MONTHLY", + "nextPayment": 730.09, + "nextPaymentDate": 1585724400000, + "description": "Description", + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072921, + "accountNumber": "8000008888", + "accountNickname": "My Auto Loan", + "displayPosition": 1, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "CHECKING", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072922, + "accountNumber": "1000001111", + "accountNickname": "My Checking", + "displayPosition": 6, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "CD", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072923, + "accountNumber": "2000005555", + "accountNickname": "My CD", + "displayPosition": 8, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "creditAccount", + "creditAccountType": "CREDITCARD", + "currentBalance": -730.09, + "paymentDueDate": 1585724400000, + "paymentMinAmount": 15, + "statementCloseBalance": -730.09, + "statementEndDate": 1583049600000, + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072924, + "accountNumber": "4100007777", + "accountNickname": "My Visa", + "displayPosition": 2, + "institutionId": 100000, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "SAVINGS", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072925, + "accountNumber": "1000002222", + "accountNickname": "My Savings", + "displayPosition": 7, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "loanAccount", + "loanType": "MORTGAGE", + "firstPaymentDate": 1585724400000, + "guarantor": "Guarantor", + "collateral": "730.09", + "currentSchool": "Cur School", + "originalSchool": "Orig School", + "lender": "Lender", + "autopayEnrolled": true, + "paymentMinAmount": 730.09, + "recurringPaymentAmount": 730.09, + "firstMortgage": false, + "loanPaymentFreq": "MONTHLY", + "nextPayment": 730.09, + "nextPaymentDate": 1585724400000, + "description": "Description", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072926, + "accountNumber": "1000000001", + "accountNickname": "My Mortgage", + "displayPosition": 5, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "TAXABLE", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072927, + "accountNumber": "0000000000", + "accountNickname": "My Brokerage", + "displayPosition": 9, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "$401K", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072928, + "accountNumber": "0000000001", + "accountNickname": "My Retirement", + "displayPosition": 10, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + } + ] +}, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 6167d84..729dbd6 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -72,6 +72,10 @@ fixtures = oauth() load "deleteCustomer" + discoverAndAddAccounts: -> + oauth() + load "discoverAndAddAccounts" + module.exports = record: -> nock.recorder.rec From 330d06cf7d44ce328b2da4d9b9d2b7a349dc92d6 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 15:50:21 -0700 Subject: [PATCH 18/24] Handle MFA challenge on discoverAndAddAccounts call --- lib/request.coffee | 19 +- test/api.coffee | 67 ++- test/fixtures/discoverAndAddAccounts.json | 414 +++++++++---------- test/fixtures/discoverAndAddAccountsMfa.json | 41 ++ test/fixtures/index.coffee | 4 + 5 files changed, 312 insertions(+), 233 deletions(-) create mode 100644 test/fixtures/discoverAndAddAccountsMfa.json diff --git a/lib/request.coffee b/lib/request.coffee index 738b04c..be4e80c 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -1,4 +1,5 @@ request = require "request" +_ = require "lodash" debug = require("debug")("node-intuit:request") OAuth = require "./oauth" SAML_URL = "https://oauth.intuit.com/oauth/v1/get_access_token_by_saml" @@ -27,14 +28,20 @@ module.exports = class Request method = if params.method is "DELETE" then params.method.toLowerCase()[0..2] else params.method.toLowerCase() requestMethod = request[method] requestMethod params, (err, response, body) -> + return done err if err debug "#{params.method} #{response.request.uri.path} - #{response.statusCode} #{response.statusMessage}" return done null, response.statusCode if response.statusCode is 200 and not body - try - parsed = JSON.parse body - catch e - err = e - parsed = body - done err, parsed + # MFA + if response.headers.challengesessionid + {challengenodeid, challengesessionid} = response.headers + done err, {accounts: _.merge(body, {challengenodeid, challengesessionid})} + else + try + parsed = JSON.parse body + catch e + err = e + parsed = body + done err, parsed get: (url, body, done) -> @_params "GET", url, (err, params) => diff --git a/test/api.coffee b/test/api.coffee index f3c8d0b..ddef575 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -26,27 +26,54 @@ describe "Intuit Client", -> done err describe "discoverAndAddAccounts", -> - before -> fixture.load "discoverAndAddAccounts" - it "should return newly created accounts", -> - loginDetails = - credentials: - credential: [ - { - name: "Banking Userid" - value: "demo" - } - - { - name: "Banking Password" - value: "go" - } - ] - intuit.discoverAndAddAccounts "userId", 100000, loginDetails, (err, accounts) -> - assert.equal accounts.length, 10 - done err + describe "Single Factor Authentication", -> + before -> fixture.load "discoverAndAddAccounts" + it "should return newly created accounts", -> + loginDetails = + credentials: + credential: [ + { + name: "Banking Userid" + value: "demo" + } + + { + name: "Banking Password" + value: "go" + } + ] + intuit.discoverAndAddAccounts "userId", 100000, loginDetails, (err, accounts) -> + assert.equal accounts.length, 10 + done err + + describe "Multi Factor Authentication", -> + before -> fixture.load "discoverAndAddAccountsMfa" + before (done) -> + loginDetails = + credentials: + credential: [ + { + name: "Banking Userid" + value: "tfa_text" + } + + { + name: "Banking Password" + value: "go" + } + ] + intuit.discoverAndAddAccounts "userId", 100000, loginDetails, (err, @mfa) => + done err + + it "should return a challenge node ID", -> + assert @mfa.challengenodeid + it "should return a challenge session ID", -> + assert @mfa.challengesessionid + it "should return the challenge to answer", -> + assert @mfa.challenge - describe "mfa", -> - it "should handle MFA" + describe "handleMfa", -> + it "should handle challenge MFA answers" describe "getCustomerAccounts", -> before -> fixture.load "getCustomerAccounts" diff --git a/test/fixtures/discoverAndAddAccounts.json b/test/fixtures/discoverAndAddAccounts.json index 46183cc..bd0cd81 100644 --- a/test/fixtures/discoverAndAddAccounts.json +++ b/test/fixtures/discoverAndAddAccounts.json @@ -6,214 +6,214 @@ "body": "", "status": 200, "response": { - "accounts": [ - { - "type": "creditAccount", - "creditAccountType": "LINEOFCREDIT", - "currentBalance": -730.09, - "currencyCode": "INR", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072919, - "accountNumber": "8000006666", - "accountNickname": "My Line of Credit", - "displayPosition": 4, - "institutionId": 100000, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 + "accounts": [ + { + "type": "creditAccount", + "creditAccountType": "LINEOFCREDIT", + "currentBalance": -730.09, + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072919, + "accountNumber": "8000006666", + "accountNickname": "My Line of Credit", + "displayPosition": 4, + "institutionId": 100000, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "TAXABLE", + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072920, + "accountNumber": "2000004444", + "accountNickname": "My Roth IRA", + "displayPosition": 3, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "loanAccount", + "loanType": "LOAN", + "firstPaymentDate": 1585724400000, + "guarantor": "Guarantor", + "collateral": "730.09", + "currentSchool": "Cur School", + "originalSchool": "Orig School", + "lender": "Lender", + "autopayEnrolled": true, + "paymentMinAmount": 730.09, + "recurringPaymentAmount": 730.09, + "firstMortgage": false, + "loanPaymentFreq": "MONTHLY", + "nextPayment": 730.09, + "nextPaymentDate": 1585724400000, + "description": "Description", + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072921, + "accountNumber": "8000008888", + "accountNickname": "My Auto Loan", + "displayPosition": 1, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "CHECKING", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072922, + "accountNumber": "1000001111", + "accountNickname": "My Checking", + "displayPosition": 6, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "CD", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072923, + "accountNumber": "2000005555", + "accountNickname": "My CD", + "displayPosition": 8, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "creditAccount", + "creditAccountType": "CREDITCARD", + "currentBalance": -730.09, + "paymentDueDate": 1585724400000, + "paymentMinAmount": 15, + "statementCloseBalance": -730.09, + "statementEndDate": 1583049600000, + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072924, + "accountNumber": "4100007777", + "accountNickname": "My Visa", + "displayPosition": 2, + "institutionId": 100000, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "SAVINGS", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072925, + "accountNumber": "1000002222", + "accountNickname": "My Savings", + "displayPosition": 7, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "loanAccount", + "loanType": "MORTGAGE", + "firstPaymentDate": 1585724400000, + "guarantor": "Guarantor", + "collateral": "730.09", + "currentSchool": "Cur School", + "originalSchool": "Orig School", + "lender": "Lender", + "autopayEnrolled": true, + "paymentMinAmount": 730.09, + "recurringPaymentAmount": 730.09, + "firstMortgage": false, + "loanPaymentFreq": "MONTHLY", + "nextPayment": 730.09, + "nextPaymentDate": 1585724400000, + "description": "Description", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072926, + "accountNumber": "1000000001", + "accountNickname": "My Mortgage", + "displayPosition": 5, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "TAXABLE", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072927, + "accountNumber": "0000000000", + "accountNickname": "My Brokerage", + "displayPosition": 9, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "$401K", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072928, + "accountNumber": "0000000001", + "accountNickname": "My Retirement", + "displayPosition": 10, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + } + ] }, - { - "type": "investmentAccount", - "currentBalance": 730.09, - "investmentAccountType": "TAXABLE", - "currencyCode": "USD", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072920, - "accountNumber": "2000004444", - "accountNickname": "My Roth IRA", - "displayPosition": 3, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "loanAccount", - "loanType": "LOAN", - "firstPaymentDate": 1585724400000, - "guarantor": "Guarantor", - "collateral": "730.09", - "currentSchool": "Cur School", - "originalSchool": "Orig School", - "lender": "Lender", - "autopayEnrolled": true, - "paymentMinAmount": 730.09, - "recurringPaymentAmount": 730.09, - "firstMortgage": false, - "loanPaymentFreq": "MONTHLY", - "nextPayment": 730.09, - "nextPaymentDate": 1585724400000, - "description": "Description", - "currencyCode": "USD", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072921, - "accountNumber": "8000008888", - "accountNickname": "My Auto Loan", - "displayPosition": 1, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "bankingAccount", - "bankingAccountType": "CHECKING", - "currencyCode": "BND", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072922, - "accountNumber": "1000001111", - "accountNickname": "My Checking", - "displayPosition": 6, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "bankingAccount", - "bankingAccountType": "CD", - "currencyCode": "INR", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072923, - "accountNumber": "2000005555", - "accountNickname": "My CD", - "displayPosition": 8, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "creditAccount", - "creditAccountType": "CREDITCARD", - "currentBalance": -730.09, - "paymentDueDate": 1585724400000, - "paymentMinAmount": 15, - "statementCloseBalance": -730.09, - "statementEndDate": 1583049600000, - "currencyCode": "USD", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072924, - "accountNumber": "4100007777", - "accountNickname": "My Visa", - "displayPosition": 2, - "institutionId": 100000, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "bankingAccount", - "bankingAccountType": "SAVINGS", - "currencyCode": "INR", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072925, - "accountNumber": "1000002222", - "accountNickname": "My Savings", - "displayPosition": 7, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "loanAccount", - "loanType": "MORTGAGE", - "firstPaymentDate": 1585724400000, - "guarantor": "Guarantor", - "collateral": "730.09", - "currentSchool": "Cur School", - "originalSchool": "Orig School", - "lender": "Lender", - "autopayEnrolled": true, - "paymentMinAmount": 730.09, - "recurringPaymentAmount": 730.09, - "firstMortgage": false, - "loanPaymentFreq": "MONTHLY", - "nextPayment": 730.09, - "nextPaymentDate": 1585724400000, - "description": "Description", - "currencyCode": "BND", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072926, - "accountNumber": "1000000001", - "accountNickname": "My Mortgage", - "displayPosition": 5, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "investmentAccount", - "currentBalance": 730.09, - "investmentAccountType": "TAXABLE", - "currencyCode": "INR", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072927, - "accountNumber": "0000000000", - "accountNickname": "My Brokerage", - "displayPosition": 9, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - }, - { - "type": "investmentAccount", - "currentBalance": 730.09, - "investmentAccountType": "$401K", - "currencyCode": "INR", - "status": "ACTIVE", - "balanceDate": 1438239600000, - "accountId": 400109072928, - "accountNumber": "0000000001", - "accountNickname": "My Retirement", - "displayPosition": 10, - "institutionId": 100000, - "balanceAmount": 730.09, - "aggrSuccessDate": 1438286977037, - "aggrAttemptDate": 1438286977037, - "aggrStatusCode": "0", - "institutionLoginId": 1281860104 - } - ] -}, "headers": { "date": "Tue, 28 Jul 2015 18:32:49 GMT", "content-type": "text/plain", diff --git a/test/fixtures/discoverAndAddAccountsMfa.json b/test/fixtures/discoverAndAddAccountsMfa.json new file mode 100644 index 0000000..e61efde --- /dev/null +++ b/test/fixtures/discoverAndAddAccountsMfa.json @@ -0,0 +1,41 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "POST", + "path": "/v1/institutions/100000/logins", + "body": { + "credentials": { + "credential": [ + { + "name": "Banking Userid", + "value": "tfa_text" + }, + { + "name": "Banking Password", + "value": "go" + } + ] + } + }, + "status": 200, + "response": { + "challenge": [ + { + "textOrImageAndChoice": [ + "Enter your first pet's name:" + ] + } + ], + "challengenodeid": "10.164.34.175", + "challengesessionid": "c6c76ab1-c0ae-4f56-8153-aeb94cecbec9" + }, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "challengesessionid": "c6c76ab1-c0ae-4f56-8153-aeb94cecbec9", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 729dbd6..0736197 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -76,6 +76,10 @@ fixtures = oauth() load "discoverAndAddAccounts" + discoverAndAddAccountsMfa: -> + oauth() + load "discoverAndAddAccountsMfa" + module.exports = record: -> nock.recorder.rec From 6b5afdf1cb04ffab7be72d505bd0f0634be05b53 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 18:02:16 -0700 Subject: [PATCH 19/24] Add handleMfa method and tests --- lib/client.coffee | 8 + lib/request.coffee | 27 +-- test/api.coffee | 27 ++- test/fixtures/discoverAndAddAccountsMfa.json | 2 +- test/fixtures/handleMfa.json | 230 +++++++++++++++++++ test/fixtures/index.coffee | 4 + 6 files changed, 279 insertions(+), 19 deletions(-) create mode 100644 test/fixtures/handleMfa.json diff --git a/lib/client.coffee b/lib/client.coffee index ee08f31..80d9782 100644 --- a/lib/client.coffee +++ b/lib/client.coffee @@ -36,6 +36,14 @@ module.exports = class IntuitClient return done err if err done err, response.accounts + handleMfa: (userId, institutionId, challengeSessionId, challengeNodeId, answers, done) -> + @options.headers = + challengeSessionId: challengeSessionId + challengeNodeId: challengeNodeId + @request "post", "/institutions/#{institutionId}/logins", answers, (err, response) -> + return done err if err + done err, response.accounts + getCustomerAccounts: (userId, done) -> @options.userId = userId @request "get", "/accounts", (err, response) -> diff --git a/lib/request.coffee b/lib/request.coffee index be4e80c..bbbdd61 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -11,17 +11,20 @@ module.exports = class Request _params: (method, url, done) -> @oauth.getToken (err, token, tokenSecret) => return done err if err + optionalHeaders = @options.headers or {} + headers = + "Content-Type": "application/json" + "Accept": "application/json" params = method: method uri: url - headers: - "Content-Type": "application/json" - "Accept": "application/json" + json: true oauth: consumer_key: @options.consumerKey consumer_secret: @options.consumerSecret token: token token_secret: tokenSecret + params.headers = _.merge headers, optionalHeaders done err, params request: (params, done) -> @@ -30,18 +33,10 @@ module.exports = class Request requestMethod params, (err, response, body) -> return done err if err debug "#{params.method} #{response.request.uri.path} - #{response.statusCode} #{response.statusMessage}" - return done null, response.statusCode if response.statusCode is 200 and not body - # MFA - if response.headers.challengesessionid - {challengenodeid, challengesessionid} = response.headers - done err, {accounts: _.merge(body, {challengenodeid, challengesessionid})} - else - try - parsed = JSON.parse body - catch e - err = e - parsed = body - done err, parsed + return done err, response.statusCode if response.statusCode is 200 and not body + return done err, body unless response.statusCode is 401 + {challengenodeid, challengesessionid} = response.headers + done err, {accounts: _.merge(body, {challengeNodeId: challengenodeid, challengeSessionId: challengesessionid})} get: (url, body, done) -> @_params "GET", url, (err, params) => @@ -52,14 +47,12 @@ module.exports = class Request @_params "POST", url, (err, params) => return done err if err params.body = body - params.json = true @request params, done put: (url, body, done) -> @_params "PUT", url, (err, params) => return done err if err params.body = body - params.json = true @request params, done delete: (url, body, done) -> diff --git a/test/api.coffee b/test/api.coffee index ddef575..c06ff7f 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -73,7 +73,32 @@ describe "Intuit Client", -> assert @mfa.challenge describe "handleMfa", -> - it "should handle challenge MFA answers" + before -> fixture.load "discoverAndAddAccountsMfa" + before -> fixture.load "handleMfa" + before (done) -> + loginDetails = + credentials: + credential: [ + { + name: "Banking Userid" + value: "tfa_text" + } + + { + name: "Banking Password" + value: "go" + } + ] + intuit.discoverAndAddAccounts "userId", 100000, loginDetails, (err, response) => + {challenge, challengeSessionId, challengeNodeId} = response + answers = + challengeResponses: + response: ["answer"] + intuit.handleMfa "userId", 100000, challengeSessionId, challengeNodeId, answers, (err, @accounts) => + done err + + it "should handle challenge MFA answers and return accounts", -> + assert.equal @accounts.length, 10 describe "getCustomerAccounts", -> before -> fixture.load "getCustomerAccounts" diff --git a/test/fixtures/discoverAndAddAccountsMfa.json b/test/fixtures/discoverAndAddAccountsMfa.json index e61efde..a55aab2 100644 --- a/test/fixtures/discoverAndAddAccountsMfa.json +++ b/test/fixtures/discoverAndAddAccountsMfa.json @@ -17,7 +17,7 @@ ] } }, - "status": 200, + "status": 401, "response": { "challenge": [ { diff --git a/test/fixtures/handleMfa.json b/test/fixtures/handleMfa.json new file mode 100644 index 0000000..4255332 --- /dev/null +++ b/test/fixtures/handleMfa.json @@ -0,0 +1,230 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "POST", + "path": "/v1/institutions/100000/logins", + "body": { + "challengeResponses": { + "response": ["answer"] + } + }, + "status": 200, + "response": { + "accounts": [ + { + "type": "creditAccount", + "creditAccountType": "LINEOFCREDIT", + "currentBalance": -730.09, + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072919, + "accountNumber": "8000006666", + "accountNickname": "My Line of Credit", + "displayPosition": 4, + "institutionId": 100000, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "TAXABLE", + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072920, + "accountNumber": "2000004444", + "accountNickname": "My Roth IRA", + "displayPosition": 3, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "loanAccount", + "loanType": "LOAN", + "firstPaymentDate": 1585724400000, + "guarantor": "Guarantor", + "collateral": "730.09", + "currentSchool": "Cur School", + "originalSchool": "Orig School", + "lender": "Lender", + "autopayEnrolled": true, + "paymentMinAmount": 730.09, + "recurringPaymentAmount": 730.09, + "firstMortgage": false, + "loanPaymentFreq": "MONTHLY", + "nextPayment": 730.09, + "nextPaymentDate": 1585724400000, + "description": "Description", + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072921, + "accountNumber": "8000008888", + "accountNickname": "My Auto Loan", + "displayPosition": 1, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "CHECKING", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072922, + "accountNumber": "1000001111", + "accountNickname": "My Checking", + "displayPosition": 6, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "CD", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072923, + "accountNumber": "2000005555", + "accountNickname": "My CD", + "displayPosition": 8, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "creditAccount", + "creditAccountType": "CREDITCARD", + "currentBalance": -730.09, + "paymentDueDate": 1585724400000, + "paymentMinAmount": 15, + "statementCloseBalance": -730.09, + "statementEndDate": 1583049600000, + "currencyCode": "USD", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072924, + "accountNumber": "4100007777", + "accountNickname": "My Visa", + "displayPosition": 2, + "institutionId": 100000, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "bankingAccount", + "bankingAccountType": "SAVINGS", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072925, + "accountNumber": "1000002222", + "accountNickname": "My Savings", + "displayPosition": 7, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "loanAccount", + "loanType": "MORTGAGE", + "firstPaymentDate": 1585724400000, + "guarantor": "Guarantor", + "collateral": "730.09", + "currentSchool": "Cur School", + "originalSchool": "Orig School", + "lender": "Lender", + "autopayEnrolled": true, + "paymentMinAmount": 730.09, + "recurringPaymentAmount": 730.09, + "firstMortgage": false, + "loanPaymentFreq": "MONTHLY", + "nextPayment": 730.09, + "nextPaymentDate": 1585724400000, + "description": "Description", + "currencyCode": "BND", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072926, + "accountNumber": "1000000001", + "accountNickname": "My Mortgage", + "displayPosition": 5, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "TAXABLE", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072927, + "accountNumber": "0000000000", + "accountNickname": "My Brokerage", + "displayPosition": 9, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + }, + { + "type": "investmentAccount", + "currentBalance": 730.09, + "investmentAccountType": "$401K", + "currencyCode": "INR", + "status": "ACTIVE", + "balanceDate": 1438239600000, + "accountId": 400109072928, + "accountNumber": "0000000001", + "accountNickname": "My Retirement", + "displayPosition": 10, + "institutionId": 100000, + "balanceAmount": 730.09, + "aggrSuccessDate": 1438286977037, + "aggrAttemptDate": 1438286977037, + "aggrStatusCode": "0", + "institutionLoginId": 1281860104 + } + ] + }, + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "challengesessionid": "c6c76ab1-c0ae-4f56-8153-aeb94cecbec9", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] \ No newline at end of file diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index 0736197..c01acaa 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -80,6 +80,10 @@ fixtures = oauth() load "discoverAndAddAccountsMfa" + handleMfa: -> + oauth() + load "handleMfa" + module.exports = record: -> nock.recorder.rec From 570888183f7471cdf3a2b894f51207b0b858e295 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 18:12:18 -0700 Subject: [PATCH 20/24] Better readme, prepared for publishing to NPM --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d06d7e..1b84525 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # node-intuit -WIP `npm` port of https://github.com/cloocher/aggcat +## Synopsis -### Basic Usage +Inspired by https://github.com/cloocher/aggcat, this is a node module for interacting +with Intuit's Customer Account Data (CAD) API. + +## Code Example ```coffeescript fs = require "fs" @@ -24,4 +27,17 @@ intuit.getInstitutionDetails userId, 100000, (err, institutionDetails) -> process.exit() ``` +## Installation + +`npm install --save node-intuit` + +## API Reference + +See the [tests](https://github.com/hellodigit/node-intuit/blob/master/test/api.coffee) +for all APIs with expected responses. + +## Tests + +`npm test` + Debug with `DEBUG=intuit*`. From 635e8049d8dfcc11e8f6e16d9e67e4a45388ca93 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 19:44:31 -0700 Subject: [PATCH 21/24] Add updateInstitutionLogin method and test --- test/api.coffee | 20 ++++++++++++++- test/fixtures/index.coffee | 4 +++ test/fixtures/updateInstitutionLogin.json | 30 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/updateInstitutionLogin.json diff --git a/test/api.coffee b/test/api.coffee index c06ff7f..48b7e0c 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -143,7 +143,25 @@ describe "Intuit Client", -> done err describe "updateInstitutionLogin", -> - it "should update bank credentials" + before -> fixture.load "updateInstitutionLogin" + it "should update bank credentials", -> + loginId = 1281950108 + loginDetails = + credentials: + credential: [ + { + name: "Banking Userid" + value: "demo" + } + + { + name: "Banking Password" + value: "go" + } + ] + intuit.updateInstitutionLogin "userId", 100000, loginId, loginDetails, (err, accounts) -> + assert.equal accounts.length, 10 + done err describe "deleteAccount", -> before -> fixture.load "deleteAccount" diff --git a/test/fixtures/index.coffee b/test/fixtures/index.coffee index c01acaa..a7864a4 100644 --- a/test/fixtures/index.coffee +++ b/test/fixtures/index.coffee @@ -84,6 +84,10 @@ fixtures = oauth() load "handleMfa" + updateInstitutionLogin: -> + oauth() + load "updateInstitutionLogin" + module.exports = record: -> nock.recorder.rec diff --git a/test/fixtures/updateInstitutionLogin.json b/test/fixtures/updateInstitutionLogin.json new file mode 100644 index 0000000..6c4b6a6 --- /dev/null +++ b/test/fixtures/updateInstitutionLogin.json @@ -0,0 +1,30 @@ +[ + { + "scope": "https://financialdatafeed.platform.intuit.com:443", + "method": "GET", + "path": "/v1/accounts/400107846787", + "body": { + "credentials": { + "credential": [ + { + "name": "Banking Userid", + "value": "direct" + }, + { + "name": "Banking Password", + "value": "go" + } + ] + } + }, + "status": 200, + "response": "", + "headers": { + "date": "Tue, 28 Jul 2015 18:32:49 GMT", + "content-type": "text/plain", + "content-length": "0", + "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", + "connection": "close" + } + } +] From 1514234b84f51e07cc082102ed6b334955111895 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 19:51:57 -0700 Subject: [PATCH 22/24] Version 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d26fa73..4f5ce19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-intuit", - "version": "0.0.1", + "version": "1.0.0", "description": "Client for Intuit's Customer Account Data API", "main": "index.js", "scripts": { From 5cb693e28a9f9f108d85d80f15490fdda1e77572 Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 20:01:17 -0700 Subject: [PATCH 23/24] MFA handling cleanup --- lib/client.coffee | 5 ++++- lib/request.coffee | 2 +- test/api.coffee | 4 ++-- test/fixtures/discoverAndAddAccountsMfa.json | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/client.coffee b/lib/client.coffee index 80d9782..92a2f41 100644 --- a/lib/client.coffee +++ b/lib/client.coffee @@ -34,7 +34,10 @@ module.exports = class IntuitClient @options.userId = userId @request "post", "/institutions/#{institutionId}/logins", credentials, (err, response) -> return done err if err - done err, response.accounts + if response.challenge + done err, response + else + done err, response.accounts handleMfa: (userId, institutionId, challengeSessionId, challengeNodeId, answers, done) -> @options.headers = diff --git a/lib/request.coffee b/lib/request.coffee index bbbdd61..18c6dab 100644 --- a/lib/request.coffee +++ b/lib/request.coffee @@ -36,7 +36,7 @@ module.exports = class Request return done err, response.statusCode if response.statusCode is 200 and not body return done err, body unless response.statusCode is 401 {challengenodeid, challengesessionid} = response.headers - done err, {accounts: _.merge(body, {challengeNodeId: challengenodeid, challengeSessionId: challengesessionid})} + done err, {challenge: body.challenge, challengeNodeId: challengenodeid, challengeSessionId: challengesessionid} get: (url, body, done) -> @_params "GET", url, (err, params) => diff --git a/test/api.coffee b/test/api.coffee index 48b7e0c..64f41c3 100644 --- a/test/api.coffee +++ b/test/api.coffee @@ -66,9 +66,9 @@ describe "Intuit Client", -> done err it "should return a challenge node ID", -> - assert @mfa.challengenodeid + assert @mfa.challengeNodeId it "should return a challenge session ID", -> - assert @mfa.challengesessionid + assert @mfa.challengeSessionId it "should return the challenge to answer", -> assert @mfa.challenge diff --git a/test/fixtures/discoverAndAddAccountsMfa.json b/test/fixtures/discoverAndAddAccountsMfa.json index a55aab2..b8e7e35 100644 --- a/test/fixtures/discoverAndAddAccountsMfa.json +++ b/test/fixtures/discoverAndAddAccountsMfa.json @@ -32,6 +32,7 @@ "headers": { "date": "Tue, 28 Jul 2015 18:32:49 GMT", "challengesessionid": "c6c76ab1-c0ae-4f56-8153-aeb94cecbec9", + "challengenodeid": "10.164.34.175", "content-type": "text/plain", "content-length": "0", "intuit_tid": "gw-1e6def67-b82b-4317-981c-d2e7bed343c8", From 51930ae28be8a962dee320e124083e8e83d7cc6b Mon Sep 17 00:00:00 2001 From: Todd Larsen Date: Thu, 30 Jul 2015 20:14:23 -0700 Subject: [PATCH 24/24] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b84525..7095ecb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ config = certificate: fs.readFileSync path.join(__dirname, "app.crt"), "utf8" certificatePassword: "password" -intuit = require("intuit")(config) +intuit = require("node-intuit")(config) intuit.getInstitutionDetails userId, 100000, (err, institutionDetails) -> console.log institutionDetails @@ -40,4 +40,4 @@ for all APIs with expected responses. `npm test` -Debug with `DEBUG=intuit*`. +Debug with `DEBUG=node-intuit*`.