old_plaid 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/PUBLISHING.md +21 -0
- data/README.md +125 -0
- data/Rakefile +8 -0
- data/lib/old_plaid.rb +100 -0
- data/lib/old_plaid/config.rb +19 -0
- data/lib/old_plaid/connection.rb +109 -0
- data/lib/old_plaid/errors.rb +27 -0
- data/lib/old_plaid/models/account.rb +24 -0
- data/lib/old_plaid/models/category.rb +17 -0
- data/lib/old_plaid/models/exchange_token_response.rb +11 -0
- data/lib/old_plaid/models/info.rb +12 -0
- data/lib/old_plaid/models/institution.rb +22 -0
- data/lib/old_plaid/models/transaction.rb +24 -0
- data/lib/old_plaid/models/user.rb +189 -0
- data/lib/old_plaid/version.rb +3 -0
- data/old_plaid.gemspec +28 -0
- data/spec/old_plaid_spec.rb +263 -0
- data/spec/plaid/config_spec.rb +67 -0
- data/spec/plaid/connection_spec.rb +191 -0
- data/spec/plaid/error_spec.rb +10 -0
- data/spec/plaid/models/account_spec.rb +37 -0
- data/spec/plaid/models/category_spec.rb +16 -0
- data/spec/plaid/models/institution_spec.rb +19 -0
- data/spec/plaid/models/transaction_spec.rb +28 -0
- data/spec/plaid/models/user_spec.rb +172 -0
- data/spec/spec_helper.rb +14 -0
- metadata +170 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class OldPlaidError < StandardError
|
3
|
+
attr_reader :code
|
4
|
+
attr_reader :resolve
|
5
|
+
|
6
|
+
def initialize(code, message, resolve)
|
7
|
+
super(message)
|
8
|
+
@code = code
|
9
|
+
@resolve = resolve
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class BadRequest < OldPlaidError
|
14
|
+
end
|
15
|
+
|
16
|
+
class Unauthorized < OldPlaidError
|
17
|
+
end
|
18
|
+
|
19
|
+
class RequestFailed < OldPlaidError
|
20
|
+
end
|
21
|
+
|
22
|
+
class NotFound < OldPlaidError
|
23
|
+
end
|
24
|
+
|
25
|
+
class ServerError < OldPlaidError
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class Account
|
3
|
+
attr_accessor :available_balance, :current_balance, :institution_type, :meta, :transactions, :numbers, :name, :id, :type, :subtype
|
4
|
+
|
5
|
+
def initialize(hash)
|
6
|
+
@id = hash['_id']
|
7
|
+
@name = hash['meta']['name'] if hash['meta']
|
8
|
+
@type = hash['type']
|
9
|
+
@meta = hash['meta']
|
10
|
+
@institution_type = hash['institution_type']
|
11
|
+
|
12
|
+
if hash['balance']
|
13
|
+
@available_balance = hash['balance']['available']
|
14
|
+
@current_balance = hash['balance']['current']
|
15
|
+
end
|
16
|
+
|
17
|
+
# Depository account only, "checkings" or "savings"
|
18
|
+
# Available on live data, but not on the test data
|
19
|
+
@subtype = hash['subtype']
|
20
|
+
|
21
|
+
@numbers = hash['numbers'] ? hash['numbers'] : 'Upgrade user to access routing information for this account'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class Category
|
3
|
+
attr_accessor :type, :hierarchy, :id
|
4
|
+
|
5
|
+
def initialize(fields = {})
|
6
|
+
@type = fields['type']
|
7
|
+
@hierarchy = fields['hierarchy']
|
8
|
+
@id = fields['id']
|
9
|
+
end
|
10
|
+
|
11
|
+
# API: semi-private
|
12
|
+
# This method takes an array returned from the API and instantiates all of the categories
|
13
|
+
def self.all(res)
|
14
|
+
res.map { |cat| new(cat) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class ExchangeTokenResponse
|
3
|
+
attr_accessor :access_token
|
4
|
+
attr_accessor :stripe_bank_account_token
|
5
|
+
|
6
|
+
def initialize(fields = {})
|
7
|
+
@access_token = fields['access_token']
|
8
|
+
@stripe_bank_account_token = fields['stripe_bank_account_token']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class Information
|
3
|
+
attr_accessor :names, :emails, :phone_numbers, :addresses
|
4
|
+
|
5
|
+
def initialize(hash)
|
6
|
+
@names = hash['names']
|
7
|
+
@emails = hash['emails']
|
8
|
+
@phone_numbers = hash['phone_numbers']
|
9
|
+
@addresses = hash['addresses']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class Institution
|
3
|
+
attr_accessor :id, :name, :type, :has_mfa, :mfa, :credentials, :products
|
4
|
+
|
5
|
+
def initialize(fields = {})
|
6
|
+
@id = fields['id']
|
7
|
+
@name = fields['name']
|
8
|
+
@type = fields['type']
|
9
|
+
@has_mfa = fields['has_mfa']
|
10
|
+
@mfa = fields['mfa']
|
11
|
+
@credentials = fields['credentials']
|
12
|
+
@products = fields['products']
|
13
|
+
end
|
14
|
+
|
15
|
+
# API: semi-private
|
16
|
+
# This method takes an array returned from the API and instantiates all of the institutions
|
17
|
+
def self.all(res)
|
18
|
+
res.map { |inst| new(inst) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module OldPlaid
|
2
|
+
class Transaction
|
3
|
+
attr_accessor :id, :account, :date, :amount, :name, :meta, :location, :pending, :score, :cat, :type, :category, :category_id, :pending_transaction
|
4
|
+
|
5
|
+
def initialize(fields = {})
|
6
|
+
@id = fields['_id']
|
7
|
+
@account = fields['_account']
|
8
|
+
@date = fields['date']
|
9
|
+
@amount = fields['amount']
|
10
|
+
@name = fields['name']
|
11
|
+
@location = fields['meta'].nil? ? {} : fields['meta']['location']
|
12
|
+
@pending = fields['pending']
|
13
|
+
@pending_transaction = fields['_pendingTransaction']
|
14
|
+
@score = fields['score']
|
15
|
+
@cat = Category.new({ 'id' => fields['category_id'], 'hierarchy' => fields['category'], 'type' => fields['type'] })
|
16
|
+
|
17
|
+
# Here for backwards compatibility only.
|
18
|
+
@type = fields['type']
|
19
|
+
@category = fields['category']
|
20
|
+
@category_id = fields['category_id']
|
21
|
+
@meta = fields['meta']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require_relative 'account'
|
2
|
+
require_relative 'transaction'
|
3
|
+
require_relative 'info'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module OldPlaid
|
7
|
+
class User
|
8
|
+
attr_accessor :accounts, :transactions, :access_token, :type, :permissions, :api_res, :pending_mfa_questions, :info, :information
|
9
|
+
|
10
|
+
# API: public
|
11
|
+
# Use this method to select the MFA method
|
12
|
+
def select_mfa_method(selection, type=nil)
|
13
|
+
type = self.type if type.nil?
|
14
|
+
auth_path = self.permissions.last + '/step'
|
15
|
+
res = Connection.post(auth_path, { options: { send_method: selection }.to_json, access_token: self.access_token, type: type })
|
16
|
+
update(res, self.permissions.last)
|
17
|
+
end
|
18
|
+
|
19
|
+
# API: public
|
20
|
+
# Use this method to send back the MFA code or answer
|
21
|
+
def mfa_authentication(auth, type = nil)
|
22
|
+
type = self.type if type.nil?
|
23
|
+
auth_path = self.permissions.last + '/step'
|
24
|
+
res = Connection.post(auth_path, { mfa: auth, access_token: self.access_token, type: type })
|
25
|
+
self.accounts = []
|
26
|
+
self.transactions = []
|
27
|
+
update(res)
|
28
|
+
end
|
29
|
+
|
30
|
+
# API: public
|
31
|
+
# Use this method to find out API levels available for this user
|
32
|
+
def permit?(auth_level)
|
33
|
+
self.permissions.include? auth_level
|
34
|
+
end
|
35
|
+
|
36
|
+
# API: public
|
37
|
+
# Use this method to upgrade a user to another api level
|
38
|
+
def upgrade(api_level=nil)
|
39
|
+
if api_level.nil?
|
40
|
+
api_level = 'auth' unless self.permit? 'auth'
|
41
|
+
api_level = 'connect' unless self.permit? 'connect'
|
42
|
+
end
|
43
|
+
res = Connection.post('upgrade', { access_token: self.access_token, upgrade_to: api_level })
|
44
|
+
|
45
|
+
# Reset accounts and transaction
|
46
|
+
self.accounts = []
|
47
|
+
self.transactions = []
|
48
|
+
update(res)
|
49
|
+
end
|
50
|
+
|
51
|
+
# API: public
|
52
|
+
# Use this method to delete a user from the OldPlaid API
|
53
|
+
def delete_user
|
54
|
+
Connection.delete('info', { access_token: self.access_token })
|
55
|
+
end
|
56
|
+
|
57
|
+
### Internal build methods
|
58
|
+
def initialize
|
59
|
+
self.accounts = []
|
60
|
+
self.transactions = []
|
61
|
+
self.permissions = []
|
62
|
+
self.access_token = ''
|
63
|
+
self.api_res = ''
|
64
|
+
self.info = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
# API: semi-private
|
68
|
+
# This class method instantiates a new Account object and updates it with the results
|
69
|
+
# from the API
|
70
|
+
def self.build(res, api_level = nil)
|
71
|
+
self.new.update(res, api_level)
|
72
|
+
end
|
73
|
+
|
74
|
+
# API: semi-private
|
75
|
+
# This method updates Account with the results returned from the API
|
76
|
+
def update(res, api_level = nil)
|
77
|
+
self.permit! api_level
|
78
|
+
|
79
|
+
if res[:msg].nil?
|
80
|
+
populate_user!(res)
|
81
|
+
clean_up_user!
|
82
|
+
else
|
83
|
+
set_mfa_request!(res)
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Internal helper methods
|
90
|
+
|
91
|
+
# API: semi-private
|
92
|
+
# Internal helper method to set the available API levels
|
93
|
+
def permit!(api_level)
|
94
|
+
return if api_level.nil? || self.permit?(api_level)
|
95
|
+
self.permissions << api_level
|
96
|
+
end
|
97
|
+
|
98
|
+
# API: semi-private
|
99
|
+
# Gets auth, connect, or info of the user
|
100
|
+
# TODO: (2.0) auth_level should be symbols instead of string
|
101
|
+
def get(auth_level, options = {})
|
102
|
+
return false unless self.permit? auth_level
|
103
|
+
case auth_level
|
104
|
+
when 'auth'
|
105
|
+
update(Connection.post('auth/get', access_token: self.access_token))
|
106
|
+
when 'connect'
|
107
|
+
payload = { access_token: self.access_token }.merge(options)
|
108
|
+
update(Connection.post('connect/get', payload))
|
109
|
+
when 'info'
|
110
|
+
update(Connection.secure_get('info', self.access_token))
|
111
|
+
else
|
112
|
+
raise "Invalid auth level: #{auth_level}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# API: semi-private
|
117
|
+
def get_auth
|
118
|
+
get('auth')
|
119
|
+
end
|
120
|
+
|
121
|
+
# API: semi-private
|
122
|
+
def get_connect(options={})
|
123
|
+
get('connect', options)
|
124
|
+
end
|
125
|
+
|
126
|
+
# API: semi-private
|
127
|
+
def get_info
|
128
|
+
get('info')
|
129
|
+
end
|
130
|
+
|
131
|
+
# API: semi-private
|
132
|
+
# Helper method to update user information
|
133
|
+
# Requires 'info' api level
|
134
|
+
def update_info(username,pass,pin=nil)
|
135
|
+
return false unless self.permit? 'info'
|
136
|
+
|
137
|
+
payload = { username: username, password: pass, access_token: self.access_token }
|
138
|
+
payload.merge!(pin: pin) if pin
|
139
|
+
update(OldPlaid.patch('info', payload))
|
140
|
+
end
|
141
|
+
|
142
|
+
# API: semi-private
|
143
|
+
# Helper method to update user balance
|
144
|
+
def update_balance
|
145
|
+
update(Connection.post('balance', { access_token: self.access_token }))
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def clean_up_user!
|
151
|
+
self.accounts.select! { |c| c.instance_of? Account }
|
152
|
+
end
|
153
|
+
|
154
|
+
def set_mfa_request!(res)
|
155
|
+
self.access_token = res[:body]['access_token']
|
156
|
+
self.pending_mfa_questions = res[:body]
|
157
|
+
self.api_res = res[:msg]
|
158
|
+
end
|
159
|
+
|
160
|
+
def populate_user!(res)
|
161
|
+
res['accounts'].each do |account|
|
162
|
+
if self.accounts.none? { |h| h.id == account['_id'] }
|
163
|
+
self.accounts << Account.new(account)
|
164
|
+
end
|
165
|
+
end if res['accounts']
|
166
|
+
|
167
|
+
res['transactions'].each do |transaction|
|
168
|
+
if self.transactions.any? { |t| t == transaction['_id'] }
|
169
|
+
owned_transaction = self.transactions.find { |h| h == transaction['_id'] }
|
170
|
+
owned_transaction.new(transaction)
|
171
|
+
else
|
172
|
+
self.transactions << Transaction.new(transaction)
|
173
|
+
end
|
174
|
+
end if res['transactions']
|
175
|
+
|
176
|
+
self.pending_mfa_questions = {}
|
177
|
+
self.information = Information.new(res['info']) if res['info']
|
178
|
+
self.api_res = 'success'
|
179
|
+
|
180
|
+
# TODO: Remove the following line when upgrading to V-2
|
181
|
+
self.info.merge!(res['info']) if res['info']
|
182
|
+
# End TODO
|
183
|
+
|
184
|
+
self.access_token = res['access_token'].split[0]
|
185
|
+
self.type = res['access_token'].split[1]
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
data/old_plaid.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'old_plaid/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'old_plaid'
|
7
|
+
spec.version = OldPlaid::VERSION
|
8
|
+
spec.authors = ['Justin Crites']
|
9
|
+
spec.email = ['crites.justin@gmail.com']
|
10
|
+
spec.summary = 'Ruby bindings for OldPlaid'
|
11
|
+
spec.description = 'Ruby gem wrapper for the OldPlaid API. Read more at the homepage, the wiki, or the plaid documentation.'
|
12
|
+
spec.homepage = 'https://github.com/plaid/plaid-ruby'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
spec.required_ruby_version = '>= 1.9.3'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec', '~>3.1'
|
24
|
+
spec.add_development_dependency 'pry'
|
25
|
+
spec.add_development_dependency 'pry-stack_explorer'
|
26
|
+
spec.add_development_dependency 'webmock'
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# Authentication flow specs - returns OldPlaid::User
|
2
|
+
|
3
|
+
describe OldPlaid do
|
4
|
+
let(:api_level) { raise "Define let(:api_level)" }
|
5
|
+
let(:username) { raise "Define let(:username)" }
|
6
|
+
let(:password) { raise "Define let(:password)" }
|
7
|
+
let(:type) { raise "Define let(:type)" }
|
8
|
+
let(:pin) { nil }
|
9
|
+
let(:options) { nil }
|
10
|
+
|
11
|
+
describe '.add_user' do
|
12
|
+
let(:user) { OldPlaid.add_user api_level, username, password, type, pin, options }
|
13
|
+
|
14
|
+
context 'with correct credentials for single user auth' do
|
15
|
+
let(:username) { 'plaid_test' }
|
16
|
+
let(:password) { 'plaid_good' }
|
17
|
+
let(:type) { 'wells' }
|
18
|
+
|
19
|
+
context 'and "connect" level of api access' do
|
20
|
+
let(:api_level) { 'connect' }
|
21
|
+
|
22
|
+
it { expect(user.accounts).not_to be_empty }
|
23
|
+
|
24
|
+
context 'with webhook' do
|
25
|
+
let(:options) { { login_only: true, webhook: 'test.com/test.endpoint.aspx' } }
|
26
|
+
it { expect(user.accounts).not_to be_empty }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when account is locked' do
|
30
|
+
let(:password) { 'plaid_locked' }
|
31
|
+
|
32
|
+
it 'raises a locked error' do
|
33
|
+
expect { user }.to raise_error(OldPlaid::RequestFailed) { |error|
|
34
|
+
expect(error.code).to eq(1205)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with connection options' do
|
40
|
+
context 'when requests pending transactions from an institution' do
|
41
|
+
let(:options) { { pending: true } }
|
42
|
+
it { expect(user.accounts).not_to be_empty }
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when login only is true' do
|
46
|
+
let(:options) { { login_only: true } }
|
47
|
+
it { expect(user.accounts).not_to be_empty }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'sets a start date for transactions' do
|
51
|
+
let(:options) { { login_only: true, start_date: '10 days ago'} }
|
52
|
+
it { expect(user.accounts).not_to be_empty }
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'sets an end date for transactions' do
|
56
|
+
let(:options) { { login_only: true, end_date: '10 days ago'} }
|
57
|
+
it { expect(user.accounts).not_to be_empty }
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'sets start and end dates for transactions' do
|
61
|
+
let(:options) { { gte: "05/10/2014" , lte: "06/10/2014" } }
|
62
|
+
it { expect(user.transactions).not_to be_nil }
|
63
|
+
end
|
64
|
+
|
65
|
+
pending 'with JSON-encoded string for options'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'and "auth" level of api access' do
|
70
|
+
let(:api_level) { 'auth' }
|
71
|
+
it { expect(user.accounts.first.numbers).not_to be_empty }
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'and "info" level of api access' do
|
75
|
+
let(:api_level) { 'info' }
|
76
|
+
it { expect(user.info).not_to be_empty }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'with incorrect credentials for single factor auth' do
|
81
|
+
# Set up correct credentials. Override with bad element
|
82
|
+
# within each context block
|
83
|
+
let(:username) { 'plaid_test' }
|
84
|
+
let(:password) { 'plaid_good' }
|
85
|
+
let(:type) { 'wells' }
|
86
|
+
|
87
|
+
context 'at "auth" level api access' do
|
88
|
+
let(:api_level) { 'auth' }
|
89
|
+
|
90
|
+
context 'using incorrect password' do
|
91
|
+
let(:password) { 'plaid_bad' }
|
92
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid credentials') }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'using incorrect username' do
|
96
|
+
let(:username) { 'plaid_bad' }
|
97
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid credentials') }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'at "connect" level api access' do
|
102
|
+
let(:api_level) { 'connect' }
|
103
|
+
|
104
|
+
context 'using incorrect password' do
|
105
|
+
let(:password) { 'plaid_bad' }
|
106
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid credentials') }
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'using incorrect username' do
|
110
|
+
let(:username) { 'plaid_bad' }
|
111
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid credentials') }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'at "info" level api access' do
|
116
|
+
let(:api_level) { 'info' }
|
117
|
+
|
118
|
+
context 'using incorrect password' do
|
119
|
+
let(:password) { 'plaid_bad' }
|
120
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid credentials') }
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'using incorrect username' do
|
124
|
+
let(:username) { 'plaid_bad' }
|
125
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid credentials') }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when institution requires PIN' do
|
131
|
+
let(:api_level) { 'connect' }
|
132
|
+
let(:username) { 'plaid_test' }
|
133
|
+
let(:password) { 'plaid_good' }
|
134
|
+
let(:type) { 'usaa' }
|
135
|
+
|
136
|
+
context 'using correct PIN' do
|
137
|
+
let(:pin) { '1234' }
|
138
|
+
it { expect(user.api_res).to eq 'Requires further authentication' }
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'using incorrect PIN' do
|
142
|
+
let(:pin) { '0000' }
|
143
|
+
it { expect { user }.to raise_error(OldPlaid::RequestFailed, 'invalid pin') }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'when institution requires MFA' do
|
148
|
+
let(:api_level) { 'connect' }
|
149
|
+
let(:username) { 'plaid_test' }
|
150
|
+
let(:password) { 'plaid_good' }
|
151
|
+
let(:type) { 'bofa' }
|
152
|
+
|
153
|
+
context 'with only standard credentials' do
|
154
|
+
it { expect(user.api_res).to eq 'Requires further authentication' }
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'with options' do
|
158
|
+
context 'with webhook' do
|
159
|
+
let(:options) { { login_only: true, webhook: 'test.com/test.endpoint.aspx' } }
|
160
|
+
it { expect(user.api_res).to eq 'Requires further authentication' }
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'requests a list of options for code based MFA' do
|
164
|
+
let(:type) { 'citi' }
|
165
|
+
let(:options) { { list: true } }
|
166
|
+
|
167
|
+
it { expect(user.pending_mfa_questions).not_to be_nil }
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '.set_user' do
|
174
|
+
subject { OldPlaid.set_user(access_token) }
|
175
|
+
let(:access_token) { 'test' }
|
176
|
+
|
177
|
+
it { expect(subject.access_token).to eq(access_token)}
|
178
|
+
|
179
|
+
context 'gets a valid user with accounts and transactions' do
|
180
|
+
let(:user) { OldPlaid.set_user('test_wells',['connect']) }
|
181
|
+
it { expect(user.transactions).not_to be_empty }
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'gets a valid user with accounts' do
|
185
|
+
let(:user) { OldPlaid.set_user('test_wells',['auth']) }
|
186
|
+
it { expect(user.accounts).not_to be_empty }
|
187
|
+
end
|
188
|
+
|
189
|
+
#TODO: Fully vet the info api endpoint for the beta functions before adding this as a supported function.
|
190
|
+
pending 'need to vet the info api endpoint' do
|
191
|
+
context 'gets a valid user with info' do
|
192
|
+
let(:user) { OldPlaid.set_user('test_wells',['info']) }
|
193
|
+
it { expect(user.accounts).to be_truthy}
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'gets a fully validated user with all access granted' do
|
197
|
+
let(:user) { OldPlaid.set_user('test_wells', ['connect', 'info', 'auth']) }
|
198
|
+
it { expect(user.transactions).to be_truthy}
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe '.exchange_token' do
|
204
|
+
subject { OldPlaid.exchange_token('test,chase,connected', 'QPO8Jo8vdDHMepg41PBwckXm4KdK1yUdmXOwK') }
|
205
|
+
|
206
|
+
it { expect(subject.access_token).to eql('test_chase') }
|
207
|
+
end
|
208
|
+
|
209
|
+
describe '.transactions' do
|
210
|
+
subject { OldPlaid.transactions(access_token, options) }
|
211
|
+
let(:access_token) { 'test_wells' }
|
212
|
+
let(:options) { nil }
|
213
|
+
|
214
|
+
context 'without options' do
|
215
|
+
it 'returns all accounts' do
|
216
|
+
expect(subject.accounts).not_to be_empty
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns all transactions' do
|
220
|
+
expect(subject.transactions).not_to be_empty
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'when filtering by account' do
|
225
|
+
let(:options) { { account: account } }
|
226
|
+
let(:account) { 'QPO8Jo8vdDHMepg41PBwckXm4KdK1yUdmXOwK' }
|
227
|
+
|
228
|
+
it 'returns a subset of transactions' do
|
229
|
+
expect(subject.transactions.size).to eql(2)
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'return only transactions from the requested account' do
|
233
|
+
expect(subject.transactions.map(&:account).uniq).to eql([account])
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context 'when filtering by date' do
|
238
|
+
let(:options) { { gte: "2014-07-24", lte: "2014-07-25" } }
|
239
|
+
|
240
|
+
it 'returns a subset of transactions' do
|
241
|
+
expect(subject.transactions.size).to eql(1)
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'return only transactions from the requested date range' do
|
245
|
+
expect(subject.transactions.map(&:date).uniq).to eql(['2014-07-24'])
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'when filtering by account and date' do
|
250
|
+
let(:options) { { account: account , gte: "2014-07-24", lte: "2014-07-25" } }
|
251
|
+
let(:account) { 'XARE85EJqKsjxLp6XR8ocg8VakrkXpTXmRdOo' }
|
252
|
+
|
253
|
+
it 'returns a subset of transactions' do
|
254
|
+
expect(subject.transactions.size).to eql(1)
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'returns only transactions from the requested account and date range' do
|
258
|
+
expect(subject.transactions.map(&:date).uniq).to eql(['2014-07-24'])
|
259
|
+
expect(subject.transactions.map(&:account).uniq).to eql([account])
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|