gocardless 0.1.0

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.
@@ -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