banker 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,5 +24,13 @@ module Banker
24
24
  def get_letter(value,index)
25
25
  value.to_s[index-1]
26
26
  end
27
+
28
+ def memorable_required(page)
29
+ page.labels.collect { |char| cleaner(char.to_s).to_i }
30
+ end
31
+
32
+ def cleaner(str)
33
+ str.gsub(/[^\d+]/, '')
34
+ end
27
35
  end
28
36
  end
@@ -1,129 +1,100 @@
1
1
  module Banker
2
- # This class allows the data retrieval of accounts for Lloyds TSB UK
3
- #
4
- # == Examples
5
- #
6
- # Retrieve Account Balance
7
- #
8
- # user_params = { username: 'Joe', password: 'password', memorable_word: 'superduper' }
9
- # lloyds = Banker::LloydsTSBUK.new(user_params)
10
- # lloyds.balance
11
- # # => [ {:current_account => { :balance => 160940,
12
- # :details => { :sort_code => "928277",
13
- # :account_number => "92837592" }}},
14
- #
15
- # {:savings_account => { :balance => 0.0,
16
- # :details => { :sort_code => "918260",
17
- # :account_number=>"91850261" }}},
18
- #
19
- # {:lloyds_tsb_platinum_mastercard => { :balance => 0.0,
20
- # :details => { :card_number => "9284710274618391" }}}
21
- # ]
22
- #
23
- class LloydsTSBUK
24
- attr_accessor :username, :password, :memorable_word, :balance, :agent
25
-
26
- LOGIN_ENDPOINT = "https://online.lloydstsb.co.uk/personal/logon/login.jsp"
27
-
28
- def initialize(args)
29
- @username = args[:username]
30
- @password = args[:password]
31
- @memorable_word = args[:memorable_word]
32
-
33
- @agent = Mechanize.new
34
- @agent.log = Logger.new 'mech.log'
35
- @agent.user_agent = 'Mozilla/5.0 (Banker)'
36
- @agent.force_default_encoding = 'utf8'
37
-
38
- @balance = authenticate
39
- end
2
+ class LloydsTSBUK < Base
40
3
 
41
- def get_memorable_word_letter(letter)
42
- @memorable_word.to_s[letter.to_i - 1]
43
- end
4
+ LOGIN_URL = "https://online.lloydstsb.co.uk/personal/logon/login.jsp"
5
+ COLLECT_URL = "https://secure2.lloydstsb.co.uk/personal/a/account_overview_personal/"
44
6
 
45
- def cleaner(str)
46
- str.gsub(/[^\d+]/, '')
47
- end
7
+ attr_accessor :accounts
48
8
 
49
- private
9
+ FIELD = {
10
+ username: 'frmLogin:strCustomerLogin_userID',
11
+ password: 'frmLogin:strCustomerLogin_pwd',
12
+ memorable_word: [
13
+ 'frmentermemorableinformation1:strEnterMemorableInformation_memInfo1',
14
+ 'frmentermemorableinformation1:strEnterMemorableInformation_memInfo2',
15
+ 'frmentermemorableinformation1:strEnterMemorableInformation_memInfo3'
16
+ ]
17
+ }
50
18
 
51
- def authenticate
52
- page = @agent.get(LOGIN_ENDPOINT)
19
+ def initialize(args={})
20
+ @keys = %w(username password memorable_word)
53
21
 
54
- # Login Page
55
- page = page.form('frmLogin') do |f|
56
- f.fields[0].value = @username
57
- f.fields[1].value = @password
58
- end.click_button
22
+ params(args)
23
+ @username = args.delete(:username)
24
+ @password = args.delete(:password)
25
+ @memorable_word = args.delete(:memorable_word)
59
26
 
60
- # Memorable Word
61
- form = page.form('frmentermemorableinformation1')
62
- memorable_set(page, form)
63
- page = @agent.submit(form, form.buttons.first)
27
+ @accounts = []
64
28
 
65
- # Accounts Page
66
- return account_return(page)
29
+ authenticate!
30
+ delivery!
67
31
  end
68
32
 
69
- def memorable_letters(page)
70
- {
71
- first: memorable_required(page)[0],
72
- second: memorable_required(page)[1],
73
- third: memorable_required(page)[2]
74
- }
75
- end
33
+ private
76
34
 
77
- def memorable_set(page, form)
78
- letters = memorable_letters(page)
35
+ def authenticate!
36
+ page = get(LOGIN_URL)
37
+ form = page.form_with(action: '/personal/primarylogin')
79
38
 
80
- form.fields[2].value = '&nbsp;' + get_memorable_word_letter(letters.fetch(:first))
81
- form.fields[3].value = '&nbsp;' + get_memorable_word_letter(letters.fetch(:second))
82
- form.fields[4].value = '&nbsp;' + get_memorable_word_letter(letters.fetch(:third))
83
- end
39
+ form[FIELD[:username]] = @username
40
+ form[FIELD[:password]] = @password
84
41
 
85
- def memorable_required(page)
86
- page.labels.collect { |char| cleaner(char.to_s) }
87
- end
42
+ page = @agent.submit(form, form.buttons.first)
88
43
 
89
- def account_name(page)
90
- page.search('div.accountDetails h2').collect {|a| a.content.downcase.gsub(/\s/, '_').to_sym }
91
- end
44
+ form = page.form_with(action: '/personal/a/logon/entermemorableinformation.jsp')
45
+ letters = memorable_required(page).delete_if { |v| v if v == 0 }
92
46
 
93
- def account_detail(page)
94
- page.search('div.accountDetails p.numbers').collect {|n| n.content }
47
+ form[FIELD[:memorable_word][0]] = '&nbsp;' + get_letter(@memorable_word, letters[0])
48
+ form[FIELD[:memorable_word][1]] = '&nbsp;' + get_letter(@memorable_word, letters[1])
49
+ form[FIELD[:memorable_word][2]] = '&nbsp;' + get_letter(@memorable_word, letters[2])
50
+
51
+ @agent.submit(form, form.buttons.first)
95
52
  end
96
53
 
97
- def account_balance(page)
98
- page.search('div.balanceActionsWrapper p.balance').collect {|b| b.content }
54
+ def name(page)
55
+ page.search('div.accountDetails h2').collect do |a|
56
+ a.content.downcase.gsub(/\s/, '_').to_sym
57
+ end
99
58
  end
100
59
 
101
- def formatted_detail(page)
102
- resp = account_detail(page).map { |detail| detail.split(',').map { |d| cleaner(d) } }
103
- resp.map! do |r|
104
- if r.length == 2
105
- { sort_code: r[0], account_number: r[1] }
106
- elsif r.length == 1
107
- { card_number: r[0] }
108
- else
109
- STDERR.puts "[Error] It seems we got account details that we did not expect! - #{r.inspect}"
110
- end
60
+ def identifier(page)
61
+ page.search('div.accountDetails p.numbers').collect do |n|
62
+ n.content.split(',').map { |d| cleaner(d) }
111
63
  end
112
64
  end
113
65
 
114
- def formatted_balance(page)
115
- account_balance(page).map do |b|
66
+ def balance(page)
67
+ bal = page.search('div.balanceActionsWrapper p.balance').collect {|b| b.content }
68
+
69
+ bal.map do |b|
116
70
  resp = cleaner(b)
117
71
  resp.empty? ? 0.00 : resp.to_i
118
72
  end
119
73
  end
120
74
 
121
- def account_return(page)
122
- resp = []
123
- account_name(page).zip(formatted_balance(page), formatted_detail(page)).each_with_index do |acc, index|
124
- resp << { acc[0] => { balance: acc[1], details: acc[2] } }
75
+ def limit(page)
76
+ page.search('div.accountBalance').inject([]) do |acc, b|
77
+ if b.content.downcase.include?('limit')
78
+ acc << cleaner(b.content.scan(/[\d.]+$/).first)
79
+ else
80
+ acc << 0.00
81
+ end
82
+ acc
83
+ end
84
+ end
85
+
86
+ def delivery!
87
+ page = get(COLLECT_URL)
88
+ name(page).zip(balance(page), identifier(page), limit(page)).each_with_index do |acc, index|
89
+ next if acc[2].nil?
90
+ uid = Digest::MD5.hexdigest("LloydsTSBUK#{acc[2].last}")
91
+ @accounts << Banker::Account.new(uid: uid,
92
+ name: "#{acc[0]}",
93
+ limit: acc[3],
94
+ amount: acc[1],
95
+ currency: 'GBP'
96
+ )
125
97
  end
126
- resp
127
98
  end
128
99
 
129
100
  end
@@ -1,3 +1,3 @@
1
1
  module Banker
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,53 +1,175 @@
1
- # -*- encoding: utf-8 -*-
2
1
  require 'spec_helper'
3
- require 'mechanize'
4
2
 
5
3
  describe Banker::LloydsTSBUK do
6
- let(:support_files) {File.expand_path('../../support/lloyds_tsb_uk/',__FILE__)}
7
- let(:login) { File.read(File.expand_path('login.jsp.html', support_files)) }
8
- let(:memorable) { File.read(File.expand_path('entermemorableinformation.jsp.html', support_files)) }
9
- let(:accounts) { File.read(File.expand_path('account_overview_personal.html', support_files)) }
10
- # let(:welcome) { File.read(File.expand_path('interstitialpage.jsp.html', support_files)) }
4
+ TEST_FIELD = {
5
+ username: 'frmLogin:strCustomerLogin_userID',
6
+ password: 'frmLogin:strCustomerLogin_pwd',
7
+ memorable_word: [
8
+ 'frmentermemorableinformation1:strEnterMemorableInformation_memInfo1',
9
+ 'frmentermemorableinformation1:strEnterMemorableInformation_memInfo2',
10
+ 'frmentermemorableinformation1:strEnterMemorableInformation_memInfo3'
11
+ ]
12
+ }
13
+
14
+ let(:mechanize) {mock('mechanize').as_null_object}
15
+ let(:form) {mock('form')}
16
+ let(:button) {mock('button')}
17
+ let(:args) { { username: 'Doe',
18
+ password: '82736',
19
+ memorable_word: 'testing' } }
20
+
21
+ subject { Banker::LloydsTSBUK }
11
22
 
12
23
  before do
13
- stub_request(:get, "https://online.lloydstsb.co.uk/personal/logon/login.jsp").
14
- to_return(:status => 200, :body => login, :headers => { "Content-Type" => "text/html" })
15
- stub_request(:post, "https://online.lloydstsb.co.uk/personal/primarylogin").
16
- to_return(:status => 200, :body => memorable, :headers => { "Content-Type" => "text/html" })
17
- stub_request(:post, "https://online.lloydstsb.co.uk/personal/a/logon/entermemorableinformation.jsp").
18
- to_return(:status => 200, :body => accounts, :headers => { "Content-Type" => "text/html" })
24
+ subject.any_instance.stub(:params)
25
+ subject.any_instance.stub(:authenticate!)
26
+ subject.any_instance.stub(:delivery!)
19
27
  end
20
28
 
21
- subject { Banker::LloydsTSBUK.new(:username => 'Joe',
22
- :password => 'password',
23
- :memorable_word => 'superduper') }
29
+ it {subject.new.should respond_to(:accounts)}
24
30
 
25
- describe '.new' do
26
- it { subject.username.should eql 'Joe' }
27
- it { subject.password.should eql 'password' }
28
- it { subject.balance.should be_a_kind_of(Array) }
31
+ describe "Parameters" do
32
+ before do
33
+ subject.any_instance.unstub(:params)
34
+ end
29
35
 
30
- it "should authenticate account login" do
31
- subject
32
- WebMock.should have_requested(:get, 'https://online.lloydstsb.co.uk/personal/logon/login.jsp')
33
- WebMock.should have_requested(:post, 'https://online.lloydstsb.co.uk/personal/primarylogin')
34
- WebMock.should have_requested(:post, 'https://online.lloydstsb.co.uk/personal/a/logon/entermemorableinformation.jsp')
36
+ it "raises InvalidParams when username is missing" do
37
+ expect{
38
+ subject.new
39
+ }.to raise_error(Banker::Error::InvalidParams,
40
+ "missing parameters `username` `password` `memorable_word` ")
35
41
  end
36
- end
37
42
 
38
- describe '.get_memorable_word_letter' do
39
- it { should respond_to(:get_memorable_word_letter) }
40
- it 'should return requested letter' do
41
- @memorable_word = 'Test'
42
- subject.get_memorable_word_letter('4').should eql 'e'
43
+ it "raises InvalidParams when password is missing" do
44
+ expect{
45
+ subject.new(username: "joe")
46
+ }.to raise_error(Banker::Error::InvalidParams,
47
+ "missing parameters `password` `memorable_word` ")
48
+ end
49
+
50
+ it "raises InvalidParams when memorable_word is missing" do
51
+ expect{
52
+ subject.new(username: "joe", password: "123456")
53
+ }.to raise_error(Banker::Error::InvalidParams,
54
+ "missing parameters `memorable_word` ")
55
+
56
+ end
57
+
58
+ it "accepts username, password and memorable_word" do
59
+ expect{
60
+ subject.new(username: "joe", password: "123456",
61
+ memorable_word: "superduper")
62
+ }.to_not raise_error
43
63
  end
44
64
  end
45
65
 
46
- describe '.cleaner' do
47
- it { should respond_to(:cleaner) }
48
- it 'should remove unwanted characters' do
49
- subject.cleaner("Test123Test").should eql '123'
66
+ describe "Method Calls" do
67
+ it "should Call params Method" do
68
+ subject.any_instance.should_receive(:params)
69
+ subject.new
70
+ end
71
+
72
+ it "should Call authenticate! Method" do
73
+ subject.any_instance.should_receive(:authenticate!)
74
+ subject.new
75
+ end
76
+
77
+ it "should Call delivery! Method" do
78
+ subject.any_instance.should_receive(:delivery!)
79
+ subject.new
50
80
  end
51
81
  end
52
82
 
83
+ describe "private" do
84
+ before do
85
+ Mechanize.stub(:new).and_return(mechanize)
86
+ Mechanize.any_instance.stub(get: mechanize)
87
+ end
88
+
89
+ describe "#authenticate!" do
90
+ before do
91
+ subject.any_instance.unstub(:authenticate!)
92
+ mechanize.stub(form_with: form.as_null_object)
93
+ mechanize.stub(:submit).and_return(mechanize)
94
+ subject.any_instance.stub(:get_letter).and_return('s')
95
+ end
96
+
97
+ it "should call to LOGIN_URL" do
98
+ mechanize.should_receive(:get).
99
+ with("https://online.lloydstsb.co.uk/personal/logon/login.jsp").
100
+ and_return(mechanize.as_null_object)
101
+
102
+ subject.new(args)
103
+ end
104
+
105
+ it 'should find the form element' do
106
+ mechanize.should_receive(:form_with).with( action: '/personal/primarylogin' ).
107
+ and_return(form.as_null_object)
108
+
109
+ subject.new(args)
110
+ end
111
+
112
+ it "should fill in the form" do
113
+ form.should_receive(:[]=).with(TEST_FIELD[:username], 'Doe')
114
+ form.should_receive(:[]=).with(TEST_FIELD[:password], '82736')
115
+
116
+ subject.new(args)
117
+ end
118
+
119
+ it "should submit" do
120
+ Mechanize.stub(:new).and_return(mechanize.as_null_object)
121
+
122
+ form.should_receive(:buttons).exactly(2).times.and_return([button])
123
+ mechanize.should_receive(:submit).exactly(2).times.with(form, button).
124
+ and_return(mechanize.as_null_object)
125
+
126
+ subject.new(args)
127
+ end
128
+
129
+ it 'should find the form element' do
130
+ mechanize.should_receive(:form_with).
131
+ with( action: '/personal/a/logon/entermemorableinformation.jsp' ).
132
+ and_return(form)
133
+
134
+ subject.new(args)
135
+ end
136
+
137
+ it "should fill in the form" do
138
+ subject.any_instance.unstub(:get_letter)
139
+ subject.any_instance.stub(:memorable_required).with(mechanize).and_return([1,2,3])
140
+
141
+ form.should_receive(:[]=).with(TEST_FIELD[:memorable_word][0], "&nbsp;t")
142
+ form.should_receive(:[]=).with(TEST_FIELD[:memorable_word][1], "&nbsp;e")
143
+ form.should_receive(:[]=).with(TEST_FIELD[:memorable_word][2], "&nbsp;s")
144
+
145
+ subject.new(args)
146
+ end
147
+ end
148
+
149
+ describe '#delivery!' do
150
+ let(:obj) { subject.new(args).accounts.first }
151
+
152
+ before do
153
+ subject.any_instance.unstub(:delivery!)
154
+ subject.any_instance.stub(:name).and_return(['A', 'B', 'C'])
155
+ subject.any_instance.stub(:identifier).and_return([['A'], ['B'], ['C']])
156
+ subject.any_instance.stub(:balance).and_return([1.00, 2.00, 3.00])
157
+ subject.any_instance.stub(:limit).and_return([1.00, 2.00, 3.00])
158
+ end
159
+
160
+ it 'should call to EXPORT_URL' do
161
+ mechanize.should_receive(:get).
162
+ with('https://secure2.lloydstsb.co.uk/personal/a/account_overview_personal/').
163
+ and_return(mechanize.as_null_object)
164
+
165
+ subject.new(args)
166
+ end
167
+
168
+ it { obj.name.should == "A" }
169
+ it { obj.uid.should == "9f0728995cad501bad95aa513f07b4e9" }
170
+ it { obj.amount.should == 1.0 }
171
+ it { obj.limit.should == 1.0 }
172
+ it { obj.currency.should == 'GBP' }
173
+ end
174
+ end
53
175
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: banker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-21 00:00:00.000000000 Z
13
+ date: 2012-04-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: mechanize
17
- requirement: &70148720229580 !ruby/object:Gem::Requirement
17
+ requirement: &70275203406640 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70148720229580
25
+ version_requirements: *70275203406640
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: ofx
28
- requirement: &70148720229080 !ruby/object:Gem::Requirement
28
+ requirement: &70275203405920 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70148720229080
36
+ version_requirements: *70275203405920
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: gem-release
39
- requirement: &70148720244820 !ruby/object:Gem::Requirement
39
+ requirement: &70275203405420 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70148720244820
47
+ version_requirements: *70275203405420
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: rspec
50
- requirement: &70148720244020 !ruby/object:Gem::Requirement
50
+ requirement: &70275203421160 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70148720244020
58
+ version_requirements: *70275203421160
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: webmock
61
- requirement: &70148720243580 !ruby/object:Gem::Requirement
61
+ requirement: &70275203420040 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,10 +66,10 @@ dependencies:
66
66
  version: '0'
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *70148720243580
69
+ version_requirements: *70275203420040
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: simplecov
72
- requirement: &70148720242920 !ruby/object:Gem::Requirement
72
+ requirement: &70275203418820 !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
75
  - - ! '>='
@@ -77,7 +77,7 @@ dependencies:
77
77
  version: '0'
78
78
  type: :development
79
79
  prerelease: false
80
- version_requirements: *70148720242920
80
+ version_requirements: *70275203418820
81
81
  description: A collection of strategies to access online bank accounts to obtain balance
82
82
  and transaction details.
83
83
  email: