gocardless 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+
4
+ module GoCardless
5
+ class Error < StandardError
6
+ end
7
+
8
+ class ApiError < Error
9
+ attr_reader :response, :code, :description
10
+
11
+ def initialize(response)
12
+ @response = response
13
+ @code = response.status
14
+ body = JSON.parse(response.body) rescue nil
15
+ if body.is_a? Hash
16
+ @description = body["error"] || response.body.strip || "Unknown error"
17
+ end
18
+ end
19
+
20
+ def to_s
21
+ "#{super} [#{self.code}] #{self.description}"
22
+ end
23
+ end
24
+
25
+ class ClientError < Error
26
+ end
27
+
28
+ class SignatureError < Error
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ module GoCardless
2
+ class Merchant < Resource
3
+ self.endpoint = '/merchants/:id'
4
+
5
+ attr_accessor :name
6
+ attr_accessor :description
7
+ attr_accessor :email
8
+ attr_accessor :first_name
9
+ attr_accessor :last_name
10
+ date_accessor :created_at
11
+
12
+ def subscriptions(params = {})
13
+ path = "/merchants/#{self.id}/subscriptions"
14
+ @client.api_get(path, params).map do |attrs|
15
+ GoCardless::Subscription.new_with_client(@client, attrs)
16
+ end
17
+ end
18
+
19
+ def pre_authorizations(params = {})
20
+ path = "/merchants/#{self.id}/pre_authorizations"
21
+ @client.api_get(path, params).map do |attrs|
22
+ GoCardless::PreAuthorization.new_with_client(@client, attrs)
23
+ end
24
+ end
25
+
26
+ def users(params = {})
27
+ path = "/merchants/#{self.id}/users"
28
+ @client.api_get(path, params).map do |attrs|
29
+ GoCardless::User.new_with_client(@client, attrs)
30
+ end
31
+ end
32
+
33
+ def bills(params = {})
34
+ path = "/merchants/#{self.id}/bills"
35
+ @client.api_get(path, params).map do |attrs|
36
+ GoCardless::Bill.new_with_client(@client, attrs)
37
+ end
38
+ end
39
+
40
+ def payments(params = {})
41
+ path = "/merchants/#{self.id}/payments"
42
+ @client.api_get(path, params).map do |attrs|
43
+ GoCardless::Payment.new_with_client(@client, attrs)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module GoCardless
2
+ class Payment < Resource
3
+ self.endpoint = '/payments/:id'
4
+
5
+ attr_accessor :amount, :currency, :status
6
+ reference_accessor :merchant_id, :user_id
7
+ date_accessor :created_at
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module GoCardless
2
+ class PreAuthorization < Resource
3
+ self.endpoint = '/pre_authorizations/:id'
4
+
5
+ attr_accessor :max_amount, :currency, :amount, :interval_length,
6
+ :interval_unit, :description
7
+
8
+ reference_accessor :merchant_id, :user_id
9
+ date_accessor :expires_at, :created_at
10
+
11
+ # Create a new bill under this pre-authorization. Similar to
12
+ # {Client#create_bill}, but only requires the amount to be specified.
13
+ #
14
+ # @option attrs [amount] amount the bill amount in pence
15
+ # @return [Bill] the created bill object
16
+ def create_bill(attrs)
17
+ Bill.new_with_client(client, attrs.merge(:source => self)).save
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,191 @@
1
+ require 'date'
2
+
3
+ module GoCardless
4
+ class Resource
5
+ def initialize(hash = {})
6
+ # Handle sub resources
7
+ sub_resource_uris = hash.delete('sub_resource_uris')
8
+ unless sub_resource_uris.nil?
9
+ # Need to define a method for each sub resource
10
+ sub_resource_uris.each do |name,uri|
11
+ uri = URI.parse(uri)
12
+
13
+ # Convert the query string to a hash
14
+ default_query = if uri.query.nil? || uri.query == ''
15
+ nil
16
+ else
17
+ Hash[CGI.parse(uri.query).map { |k,v| [k,v.first] }]
18
+ end
19
+
20
+ # Strip api prefix from path
21
+ path = uri.path.sub(%r{^/api/v\d+}, '')
22
+
23
+ # Modify the instance's metaclass to add the method
24
+ metaclass = class << self; self; end
25
+ metaclass.send(:define_method, name) do |*args|
26
+ # 'name' will be something like 'bills', convert it to Bill and
27
+ # look up the resource class with that name
28
+ class_name = Utils.camelize(Utils.singularize(name.to_s))
29
+ klass = GoCardless.const_get(class_name)
30
+ # Convert the results to instances of the looked-up class
31
+ params = args.first || {}
32
+ query = default_query.nil? ? nil : default_query.merge(params)
33
+ client.api_get(path, query).map do |attrs|
34
+ klass.new(attrs).tap { |m| m.client = client }
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # Set resource attribute values
41
+ hash.each { |key,val| send("#{key}=", val) if respond_to?("#{key}=") }
42
+ end
43
+
44
+ attr_writer :client
45
+
46
+ class << self
47
+ attr_accessor :endpoint
48
+
49
+ def new_with_client(client, attrs = {})
50
+ self.new(attrs).tap { |obj| obj.client = client }
51
+ end
52
+
53
+ def find_with_client(client_obj, id)
54
+ path = endpoint.gsub(':id', id.to_s)
55
+ data = client_obj.api_get(path)
56
+ obj = self.new(data)
57
+ obj.client = client_obj
58
+ obj
59
+ end
60
+
61
+ def find(id)
62
+ message = "Merchant details not found, set GoCardless.account_details"
63
+ raise Error, message unless GoCardless.client
64
+ self.find_with_client(GoCardless.client, id)
65
+ end
66
+
67
+ def date_writer(*args)
68
+ args.each do |attr|
69
+ define_method("#{attr.to_s}=".to_sym) do |date|
70
+ date = date.is_a?(String) ? DateTime.parse(date) : date
71
+ instance_variable_set("@#{attr}", date)
72
+ end
73
+ end
74
+ end
75
+
76
+ def date_accessor(*args)
77
+ attr_reader *args
78
+ date_writer *args
79
+ end
80
+
81
+ def reference_reader(*args)
82
+ attr_reader *args
83
+
84
+ args.each do |attr|
85
+ if !attr.to_s.end_with?('_id')
86
+ raise ArgumentError, 'reference_reader args must end with _id'
87
+ end
88
+
89
+ name = attr.to_s.sub(/_id$/, '')
90
+ define_method(name.to_sym) do
91
+ obj_id = instance_variable_get("@#{attr}")
92
+ klass = GoCardless.const_get(Utils.camelize(name))
93
+ klass.find_with_client(client, obj_id)
94
+ end
95
+ end
96
+ end
97
+
98
+ def reference_writer(*args)
99
+ attr_writer *args
100
+
101
+ args.each do |attr|
102
+ if !attr.to_s.end_with?('_id')
103
+ raise ArgumentError, 'reference_writer args must end with _id'
104
+ end
105
+
106
+ name = attr.to_s.sub(/_id$/, '')
107
+ define_method("#{name}=".to_sym) do |obj|
108
+ klass = GoCardless.const_get(Utils.camelize(name))
109
+ if !obj.is_a?(klass)
110
+ raise ArgumentError, "Object must be an instance of #{klass}"
111
+ end
112
+
113
+ instance_variable_set("@#{attr}", obj.id)
114
+ end
115
+ end
116
+ end
117
+
118
+ def reference_accessor(*args)
119
+ reference_reader *args
120
+ reference_writer *args
121
+ end
122
+
123
+ def creatable(val = true)
124
+ @creatable = val
125
+ end
126
+
127
+ def updatable(val = true)
128
+ @updatable = val
129
+ end
130
+
131
+ def creatable?
132
+ !!@creatable
133
+ end
134
+
135
+ def updatable?
136
+ !!@updatable
137
+ end
138
+ end
139
+
140
+
141
+ # @macro [attach] resource.property
142
+ # @return [String] the $1 property of the object
143
+ attr_accessor :id
144
+ attr_accessor :uri
145
+
146
+ def to_hash
147
+ attrs = instance_variables.map { |v| v.to_s.sub(/^@/, '') }
148
+ attrs.delete 'client'
149
+ Hash[attrs.select { |v| respond_to? v }.map { |v| [v.to_sym, send(v)] }]
150
+ end
151
+
152
+ def to_json
153
+ to_hash.to_json
154
+ end
155
+
156
+ def inspect
157
+ "#<#{self.class} #{to_hash.map { |k,v| "#{k}=#{v.inspect}" }.join(', ')}>"
158
+ end
159
+
160
+ def persisted?
161
+ !id.nil?
162
+ end
163
+
164
+ # Save a resource on the API server. If the resource already exists (has a
165
+ # non-null id), it will be updated with a PUT, otherwise it will be created
166
+ # with a POST.
167
+ def save
168
+ save_data self.to_hash
169
+ self
170
+ end
171
+
172
+ protected
173
+
174
+ def client
175
+ @client || GoCardless.client
176
+ end
177
+
178
+ def save_data(data)
179
+ method = if self.persisted?
180
+ raise "#{self.class} cannot be updated" unless self.class.updatable?
181
+ 'put'
182
+ else
183
+ raise "#{self.class} cannot be created" unless self.class.creatable?
184
+ 'post'
185
+ end
186
+ path = self.class.endpoint.gsub(':id', id.to_s)
187
+ response = client.send("api_#{method}", path, data)
188
+ response.each { |key,val| send("#{key}=", val) if respond_to?("#{key}=") } if response.is_a? Hash
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,15 @@
1
+ module GoCardless
2
+ class Subscription < Resource
3
+
4
+ self.endpoint = '/subscriptions/:id'
5
+
6
+ attr_accessor :amount, :currency, :interval_length, :interval_unit,
7
+ :description, :setup_fee, :trial_length, :trial_unit
8
+
9
+ reference_accessor :merchant_id, :user_id
10
+
11
+ date_accessor :expires_at, :created_at
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,8 @@
1
+ module GoCardless
2
+ class User < Resource
3
+ self.endpoint = '/users/:id'
4
+
5
+ attr_accessor :name, :first_name, :last_name, :email, :display_name
6
+ date_accessor :created_at
7
+ end
8
+ end
@@ -0,0 +1,36 @@
1
+
2
+ module GoCardless
3
+ module Utils
4
+ class << self
5
+
6
+ # String Helpers
7
+ def camelize(str)
8
+ str.split('_').map(&:capitalize).join
9
+ end
10
+
11
+ def underscore(str)
12
+ str.gsub(/(.)([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase
13
+ end
14
+
15
+ def singularize(str)
16
+ # This should probably be a bit more robust
17
+ str.sub(/s$/, '').sub(/i$/, 'us')
18
+ end
19
+
20
+ # Hash Helpers
21
+ def symbolize_keys(hash)
22
+ symbolize_keys! hash.dup
23
+ end
24
+
25
+ def symbolize_keys!(hash)
26
+ hash.keys.each do |key|
27
+ sym_key = key.to_s.to_sym rescue key
28
+ hash[sym_key] = hash.delete(key) unless hash.key?(sym_key)
29
+ end
30
+ hash
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,3 @@
1
+ module GoCardless
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/spec/bill_spec.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoCardless::Bill do
4
+ before :each do
5
+ @app_id = 'abc'
6
+ @app_secret = 'xyz'
7
+ GoCardless.account_details = {:app_id => @app_id, :app_secret => @app_secret,
8
+ :token => 'xxx manage_merchant:1'}
9
+ @client = GoCardless.client
10
+ end
11
+
12
+ it "source getter works" do
13
+ b = GoCardless::Bill.new(:source_type => :subscription, :source_id => 123)
14
+ @client.access_token = 'TOKEN manage_merchant:123'
15
+ stub_get(@client, :id => 123)
16
+ source = b.source
17
+ source.should be_a GoCardless::Subscription
18
+ source.id.should == 123
19
+ end
20
+
21
+ it "source setter works" do
22
+ b = GoCardless::Bill.new
23
+ b.source = GoCardless::Subscription.new(:id => 123)
24
+ b.source_id.should == 123
25
+ b.source_type.should.to_s == 'subscription'
26
+ end
27
+ end
@@ -0,0 +1,376 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoCardless::Client do
4
+ before :each do
5
+ @app_id = 'abc'
6
+ @app_secret = 'xyz'
7
+ @redirect_uri = 'http://test.com/cb'
8
+ end
9
+
10
+ describe ".base_url" do
11
+ it "returns the correct url for the production environment" do
12
+ GoCardless.environment = :production
13
+ GoCardless::Client.base_url.should == 'https://gocardless.com'
14
+ end
15
+
16
+ it "returns the correct url for the sandbox environment" do
17
+ GoCardless.environment = :sandbox
18
+ GoCardless::Client.base_url.should == 'https://sandbox.gocardless.com'
19
+ end
20
+
21
+ it "returns the correct url when it's set manually" do
22
+ GoCardless::Client.base_url = 'https://abc.gocardless.com'
23
+ GoCardless::Client.base_url.should == 'https://abc.gocardless.com'
24
+ end
25
+ end
26
+
27
+ describe "#new" do
28
+ it "without an app id should raise an error" do
29
+ lambda do
30
+ GoCardless::Client.new({:app_secret => @app_secret})
31
+ end.should raise_exception(GoCardless::ClientError)
32
+ end
33
+ it "without an app_secret should raise an error" do
34
+ lambda do
35
+ GoCardless::Client.new({:app_id => @app_id})
36
+ end.should raise_exception(GoCardless::ClientError)
37
+ end
38
+ end
39
+
40
+ before do
41
+ @client = GoCardless::Client.new({:app_id => @app_id, :app_secret => @app_secret})
42
+ end
43
+
44
+ describe "#authorize_url" do
45
+ it "fails without a redirect uri" do
46
+ lambda { @client.authorize_url }.should raise_exception(ArgumentError)
47
+ end
48
+
49
+ it "generates the authorize url correctly" do
50
+ url = URI.parse(@client.authorize_url(:redirect_uri => @redirect_uri))
51
+ query = CGI.parse(url.query)
52
+ query['response_type'].first.should == 'code'
53
+ query['redirect_uri'].first.should == @redirect_uri
54
+ query['client_id'].first.should == @app_id
55
+ end
56
+ end
57
+
58
+ describe "#fetch_access_token" do
59
+ access_token_url = "#{GoCardless::Client.base_url}/oauth/access_token"
60
+
61
+ it "fails without a redirect uri" do
62
+ lambda do
63
+ @client.fetch_access_token('code', {})
64
+ end.should raise_exception(ArgumentError)
65
+ end
66
+
67
+ describe "with valid params" do
68
+ it "calls correct method with correct args" do
69
+ auth_code = 'fakecode'
70
+ access_token = mock
71
+
72
+ @client.instance_variable_get(:@access_token).should be_nil
73
+
74
+ oauth_client = @client.instance_variable_get(:@oauth_client)
75
+ oauth_client.auth_code.expects(:get_token).with(
76
+ auth_code, has_entry(:redirect_uri => @redirect_uri)
77
+ )
78
+
79
+ @client.fetch_access_token(auth_code, {:redirect_uri => @redirect_uri})
80
+ end
81
+
82
+ it "sets @access_token" do
83
+ access_token = mock
84
+ access_token.stubs(:params).returns('scope' => '')
85
+ access_token.stubs(:token).returns('')
86
+
87
+ oauth_client = @client.instance_variable_get(:@oauth_client)
88
+ oauth_client.auth_code.expects(:get_token).returns(access_token)
89
+
90
+ @client.instance_variable_get(:@access_token).should be_nil
91
+ @client.fetch_access_token('code', {:redirect_uri => @redirect_uri})
92
+ @client.instance_variable_get(:@access_token).should == access_token
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "#access_token" do
98
+ it "serializes access token correctly" do
99
+ oauth_client = @client.instance_variable_get(:@oauth_client)
100
+ token = OAuth2::AccessToken.new(oauth_client, 'TOKEN123')
101
+ token.params['scope'] = 'a:1 b:2'
102
+ @client.instance_variable_set(:@access_token, token)
103
+
104
+ @client.access_token.should == 'TOKEN123 a:1 b:2'
105
+ end
106
+
107
+ it "returns nil when there's no token" do
108
+ @client.access_token.should be_nil
109
+ end
110
+ end
111
+
112
+ describe "#access_token=" do
113
+ it "deserializes access token correctly" do
114
+ @client.access_token = 'TOKEN123 a:1 b:2'
115
+ token = @client.instance_variable_get(:@access_token)
116
+ token.token.should == 'TOKEN123'
117
+ token.params['scope'].should == 'a:1 b:2'
118
+ end
119
+
120
+ it "handles invalid values correctly" do
121
+ token = 'TOKEN123' # missing scope
122
+ expect { @client.access_token = token }.to raise_exception ArgumentError
123
+ end
124
+ end
125
+
126
+ describe "#api_get" do
127
+ it "uses the correct path prefix" do
128
+ @client.access_token = 'TOKEN123 a:1 b:2'
129
+ token = @client.instance_variable_get(:@access_token)
130
+ r = mock
131
+ r.stubs(:parsed)
132
+ token.expects(:get).with { |p,o| p =~ %r|/api/v1/test| }.returns(r)
133
+ @client.api_get('/test')
134
+ end
135
+
136
+ it "fails without an access_token" do
137
+ expect { @client.api_get '/' }.to raise_exception GoCardless::ClientError
138
+ end
139
+ end
140
+
141
+ describe "#api_post" do
142
+ it "encodes data to json" do
143
+ @client.access_token = 'TOKEN123 a:1 b:2'
144
+ token = @client.instance_variable_get(:@access_token)
145
+ r = mock
146
+ r.stubs(:parsed)
147
+ token.expects(:post).with { |p,opts| opts[:body] == '{"a":1}' }.returns(r)
148
+ @client.api_post('/test', {:a => 1})
149
+ end
150
+
151
+ it "fails without an access_token" do
152
+ expect { @client.api_get '/' }.to raise_exception GoCardless::ClientError
153
+ end
154
+ end
155
+
156
+ describe "#merchant" do
157
+ it "looks up the correct merchant" do
158
+ @client.access_token = 'TOKEN a manage_merchant:123 b'
159
+ response = mock
160
+ response.expects(:parsed)
161
+
162
+ token = @client.instance_variable_get(:@access_token)
163
+ merchant_url = '/api/v1/merchants/123'
164
+ token.expects(:get).with { |p,o| p == merchant_url }.returns response
165
+
166
+ GoCardless::Merchant.stubs(:new_with_client)
167
+
168
+ @client.merchant
169
+ end
170
+
171
+ it "creates a Merchant object" do
172
+ @client.access_token = 'TOKEN manage_merchant:123'
173
+ response = mock
174
+ response.expects(:parsed).returns({:name => 'test', :id => 123})
175
+
176
+ token = @client.instance_variable_get(:@access_token)
177
+ token.expects(:get).returns response
178
+
179
+ merchant = @client.merchant
180
+ merchant.should be_an_instance_of GoCardless::Merchant
181
+ merchant.id.should == 123
182
+ merchant.name.should == 'test'
183
+ end
184
+ end
185
+
186
+ %w{subscription pre_authorization user bill payment}.each do |resource|
187
+ describe "##{resource}" do
188
+ it "returns the correct #{GoCardless::Utils.camelize(resource)} object" do
189
+ @client.access_token = 'TOKEN manage_merchant:123'
190
+ stub_get(@client, {:id => 123})
191
+ obj = @client.send(resource, 123)
192
+ obj.should be_a GoCardless.const_get(GoCardless::Utils.camelize(resource))
193
+ obj.id.should == 123
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "#encode_params" do
199
+ it "correctly encodes hashes" do
200
+ params = {:a => {:b => :c}, :x => :y}
201
+ result = 'a%5Bb%5D=c&x=y'
202
+ @client.send(:encode_params, params).should == result
203
+ end
204
+
205
+ it "correctly encodes arrays" do
206
+ params = {:a => [1,2]}
207
+ result = 'a%5B%5D=1&a%5B%5D=2'
208
+ @client.send(:encode_params, params).should == result
209
+ end
210
+
211
+ it "sorts params by key" do
212
+ params = {:b => 1, :a => 2}
213
+ result = 'a=2&b=1'
214
+ @client.send(:encode_params, params).should == result
215
+ end
216
+ end
217
+
218
+ it "#sign_params signs pararmeter hashes correctly" do
219
+ @client.instance_variable_set(:@app_secret, 'testsecret')
220
+ params = {:test => true}
221
+ sig = '6e4613b729ce15c288f70e72463739feeb05fc0b89b55d248d7f259b5367148b'
222
+ @client.send(:sign_params, params)[:signature].should == sig
223
+ end
224
+
225
+ describe "#signature_valid?" do
226
+ before(:each) { @params = { :x => 'y', :a => 'b' } }
227
+
228
+ it "succeeds with a valid signature" do
229
+ params = @client.send(:sign_params, @params)
230
+ @client.send(:signature_valid?, params).should be_true
231
+ end
232
+
233
+ it "fails with an invalid signature" do
234
+ params = {:signature => 'invalid'}.merge(@params)
235
+ @client.send(:signature_valid?, params).should be_false
236
+ end
237
+ end
238
+
239
+ describe "#confirm_resource" do
240
+ before :each do
241
+ @params = {
242
+ :resource_id => '1',
243
+ :resource_uri => 'a',
244
+ :resource_type => 'subscription',
245
+ }
246
+ end
247
+
248
+ [:resource_id, :resource_uri, :resource_type].each do |param|
249
+ it "fails when :#{param} is missing" do
250
+ p = @params.tap { |d| d.delete(param) }
251
+ expect { @client.confirm_resource p }.to raise_exception ArgumentError
252
+ end
253
+ end
254
+
255
+ it "doesn't confirm the resource when the signature is invalid" do
256
+ @client.expects(:request).never
257
+ @client.confirm_resource({:signature => 'xxx'}.merge(@params)) rescue nil
258
+ end
259
+
260
+ it "fails when the signature is invalid" do
261
+ expect do
262
+ @client.confirm_resource({:signature => 'xxx'}.merge(@params))
263
+ end.to raise_exception GoCardless::SignatureError
264
+ end
265
+
266
+ it "confirms the resource when the signature is valid" do
267
+ # Once for confirm, once to fetch result
268
+ @client.expects(:request).twice.returns(stub(:parsed => {}))
269
+ @client.confirm_resource(@client.send(:sign_params, @params))
270
+ end
271
+
272
+ it "returns the correct object when the signature is valid" do
273
+ @client.stubs(:request).returns(stub(:parsed => {}))
274
+ subscription = GoCardless::Subscription.new_with_client @client
275
+ GoCardless::Subscription.expects(:find_with_client).returns subscription
276
+
277
+ # confirm_resource should use the Subcription class because
278
+ # the :response_type is set to subscription
279
+ resource = @client.confirm_resource(@client.send(:sign_params, @params))
280
+ resource.should be_a GoCardless::Subscription
281
+ end
282
+
283
+ it "includes valid http basic credentials" do
284
+ GoCardless::Subscription.stubs(:find_with_client)
285
+ auth = 'Basic YWJjOnh5eg=='
286
+ @client.expects(:request).once.with do |type, path, opts|
287
+ opts.should include :headers
288
+ opts[:headers].should include 'Authorization'
289
+ opts[:headers]['Authorization'].should == auth
290
+ end
291
+ @client.confirm_resource(@client.send(:sign_params, @params))
292
+ end
293
+
294
+ it "works with string params" do
295
+ @client.stubs(:request)
296
+ GoCardless::Subscription.stubs(:find_with_client)
297
+ params = Hash[@params.dup.map { |k,v| [k.to_s, v] }]
298
+ params.keys.each { |p| p.should be_a String }
299
+ # No ArgumentErrors should be raised
300
+ @client.confirm_resource(@client.send(:sign_params, params))
301
+ end
302
+ end
303
+
304
+ it "#generate_nonce should generate a random string" do
305
+ @client.send(:generate_nonce).should_not == @client.send(:generate_nonce)
306
+ end
307
+
308
+ describe "#new_limit_url" do
309
+ before(:each) do
310
+ @merchant_id = '123'
311
+ @client.access_token = "TOKEN manage_merchant:#{@merchant_id}"
312
+ end
313
+
314
+ def get_params(url)
315
+ Hash[CGI.parse(URI.parse(url).query).map{ |k,v| [k, v.first] }]
316
+ end
317
+
318
+ it "should use the correct path" do
319
+ url = @client.send(:new_limit_url, :test_limit, {})
320
+ URI.parse(url).path.should == '/connect/test_limits/new'
321
+ end
322
+
323
+ it "should include the params in the URL query" do
324
+ params = { 'a' => '1', 'b' => '2' }
325
+ url = @client.send(:new_limit_url, :subscription, params)
326
+ url_params = get_params(url)
327
+ params.each do |key, value|
328
+ url_params["subscription[#{key}]"].should == value
329
+ end
330
+ end
331
+
332
+ it "should add merchant_id to the limit" do
333
+ url = @client.send(:new_limit_url, :subscription, {})
334
+ get_params(url)['subscription[merchant_id]'].should == @merchant_id
335
+ end
336
+
337
+ it "should include a valid signature" do
338
+ params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
339
+ params.key?('signature').should be_true
340
+ sig = params.delete('signature')
341
+ sig.should == @client.send(:sign_params, params.clone)[:signature]
342
+ end
343
+
344
+ it "should include a nonce" do
345
+ params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
346
+ params['nonce'].should be_a String
347
+ end
348
+
349
+ it "should include a client_id" do
350
+ params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
351
+ params['client_id'].should == @client.instance_variable_get(:@app_id)
352
+ end
353
+
354
+ it "should include a timestamp" do
355
+ # Time.now returning Pacific time
356
+ time = Time.parse('Sat Jan 01 00:00:00 -0800')
357
+ Time.expects(:now).returns time
358
+ params = get_params(@client.send(:new_limit_url, :subscription, :x => 1))
359
+ # Check that timezone is ISO formatted UTC
360
+ params['timestamp'].should == "2011-01-01T08:00:00Z"
361
+ end
362
+ end
363
+
364
+ describe "#merchant_id" do
365
+ it "returns the merchant id when an access token is set" do
366
+ @client.access_token = 'TOKEN manage_merchant:123'
367
+ @client.send(:merchant_id).should == '123'
368
+ end
369
+
370
+ it "fails if there's no access token" do
371
+ expect do
372
+ @client.send(:merchant_id)
373
+ end.to raise_exception GoCardless::ClientError
374
+ end
375
+ end
376
+ end