paid 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +6 -0
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/lib/paid.rb +32 -207
  6. data/lib/paid/account.rb +22 -10
  7. data/lib/paid/api_list.rb +56 -17
  8. data/lib/paid/api_method.rb +93 -0
  9. data/lib/paid/api_resource.rb +130 -8
  10. data/lib/paid/customer.rb +104 -40
  11. data/lib/paid/errors/api_connection_error.rb +1 -1
  12. data/lib/paid/errors/api_error.rb +25 -3
  13. data/lib/paid/errors/authentication_error.rb +1 -1
  14. data/lib/paid/errors/paid_error.rb +2 -9
  15. data/lib/paid/event.rb +28 -17
  16. data/lib/paid/event_data.rb +10 -0
  17. data/lib/paid/headers_builder.rb +75 -0
  18. data/lib/paid/invoice.rb +55 -16
  19. data/lib/paid/params_builder.rb +26 -0
  20. data/lib/paid/path_builder.rb +38 -0
  21. data/lib/paid/plan.rb +38 -13
  22. data/lib/paid/refund_list.rb +26 -0
  23. data/lib/paid/requester.rb +97 -0
  24. data/lib/paid/subscription.rb +47 -16
  25. data/lib/paid/transaction.rb +64 -16
  26. data/lib/paid/util.rb +14 -40
  27. data/paid.gemspec +1 -1
  28. data/test/paid/{api_class_test.rb → _api_resource_test.rb} +31 -17
  29. data/test/paid/account_test.rb +3 -3
  30. data/test/paid/api_list_test.rb +14 -8
  31. data/test/paid/api_method_test.rb +89 -0
  32. data/test/paid/customer_test.rb +20 -10
  33. data/test/paid/event_test.rb +3 -4
  34. data/test/paid/headers_builder_test.rb +39 -0
  35. data/test/paid/invoice_test.rb +3 -3
  36. data/test/paid/params_builder_test.rb +57 -0
  37. data/test/paid/path_builder_test.rb +67 -0
  38. data/test/paid/plan_test.rb +3 -3
  39. data/test/paid/requester_test.rb +86 -0
  40. data/test/paid/subscription_test.rb +3 -3
  41. data/test/paid/transaction_test.rb +4 -4
  42. data/test/paid/util_test.rb +36 -35
  43. data/test/test_data.rb +9 -2
  44. data/test/test_helper.rb +14 -14
  45. metadata +23 -19
  46. data/lib/paid/api_class.rb +0 -338
  47. data/lib/paid/api_singleton.rb +0 -5
  48. data/lib/paid/errors/invalid_request_error.rb +0 -10
  49. data/test/mock_resource.rb +0 -69
  50. data/test/paid/api_resource_test.rb +0 -28
  51. data/test/paid/api_singleton_test.rb +0 -12
  52. data/test/paid/authentication_test.rb +0 -50
  53. data/test/paid/status_codes_test.rb +0 -63
data/lib/paid/util.rb CHANGED
@@ -1,46 +1,6 @@
1
1
  module Paid
2
2
  module Util
3
3
 
4
- def self.query_string(params)
5
- if params && params.any?
6
- return query_array(params).join('&')
7
- else
8
- return ""
9
- end
10
- end
11
-
12
- # Three major use cases (and nesting of them needs to be supported):
13
- # { :a => { :b => "bvalue" } } => ["a[b]=bvalue"]
14
- # { :a => [1, 2] } => ["a[]=1", "a[]=2"]
15
- # { :a => "value" } => ["a=value"]
16
- def self.query_array(params, key_prefix=nil)
17
- ret = []
18
- params.each do |key, value|
19
- if params.is_a?(Array)
20
- value = key
21
- key = ''
22
- end
23
- key_suffix = escape(key)
24
- full_key = key_prefix ? "#{key_prefix}[#{key_suffix}]" : key_suffix
25
-
26
- if value.is_a?(Hash) || value.is_a?(Array)
27
- # Handles the following cases:
28
- # { :a => { :b => "bvalue" } } => ["a[b]=bvalue"]
29
- # { :a => [1, 2] } => ["a[]=1", "a[]=2"]
30
- ret += query_array(value, full_key)
31
- else
32
- # Handles the base case with just key and value:
33
- # { :a => "value" } => ["a=value"]
34
- ret << "#{full_key}=#{escape(value)}"
35
- end
36
- end
37
- ret
38
- end
39
-
40
- def self.escape(val)
41
- URI.escape(val.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
42
- end
43
-
44
4
  def self.symbolize_keys(obj)
45
5
  if obj.is_a?(Hash)
46
6
  ret = {}
@@ -73,5 +33,19 @@ module Paid
73
33
  end
74
34
  end
75
35
 
36
+ def self.constantize(str, prefix=false)
37
+ str = str.to_s
38
+ begin
39
+ str.split('::').reduce(Module, :const_get)
40
+ rescue NameError => e
41
+ if prefix
42
+ raise e
43
+ else
44
+ p = "#{self.name}".split("::").first
45
+ constantize("#{p}::#{str}", true)
46
+ end
47
+ end
48
+ end
49
+
76
50
  end
77
51
  end
data/paid.gemspec CHANGED
@@ -8,7 +8,7 @@ spec = Gem::Specification.new do |s|
8
8
  s.description = 'Paid is the programmatic way to manage payments. See https://paidapi.com for details.'
9
9
  s.homepage = 'http://docs.paidapi.com'
10
10
  s.authors = ['Jon Calhoun', 'Ryan Jackson']
11
- s.email = ['joncalhoun@gmail.com', 'ryan@paidapi.com']
11
+ s.email = ['jon@apibits.com', 'ryan@paidapi.com']
12
12
  s.version = Paid::VERSION
13
13
  s.license = 'MIT'
14
14
 
@@ -2,7 +2,7 @@
2
2
  require File.expand_path('../../test_helper', __FILE__)
3
3
 
4
4
  module Paid
5
- class ApiClassTest < Test::Unit::TestCase
5
+ class APIResourceTest < ::Test::Unit::TestCase
6
6
 
7
7
  context 'Non-network actions' do
8
8
  setup do
@@ -12,11 +12,11 @@ module Paid
12
12
  @mock.expects(:delete).never
13
13
  end
14
14
 
15
- should 'not fetch over the network when creating a new APIClass' do
15
+ should 'not fetch over the network when creating a new APIResource' do
16
16
  MockResource.new('fake_id')
17
17
  end
18
18
 
19
- should 'not fetch over the network when creating a new APIClass from a hash' do
19
+ should 'not fetch over the network when creating a new APIResource from a hash' do
20
20
  MockResource.construct(test_mock_resource)
21
21
  end
22
22
 
@@ -84,7 +84,7 @@ module Paid
84
84
  end
85
85
  end
86
86
 
87
- context 'APIClass :default_params' do
87
+ context 'APIResource :default_params' do
88
88
  context 'api_instance_method' do
89
89
  setup do
90
90
  @response = test_response(test_mock_resource_list)
@@ -199,7 +199,7 @@ module Paid
199
199
  end
200
200
 
201
201
 
202
- context 'APIClass#attribute' do
202
+ context 'APIResource#attribute' do
203
203
  should 'create a getter method' do
204
204
  assert(MockResource.method_defined?(:name))
205
205
  end
@@ -210,52 +210,66 @@ module Paid
210
210
 
211
211
  should 'have no changed attributes after init' do
212
212
  mr = MockResource.new(test_mock_resource)
213
- assert(mr.changed_attributes.empty?)
213
+ assert(mr.changed_api_attributes.empty?)
214
214
  end
215
215
 
216
216
  should 'keep track of changed attributes' do
217
217
  mr = MockResource.new(test_mock_resource)
218
- assert(mr.changed_attributes.empty?)
218
+ assert(mr.changed_api_attributes.empty?)
219
219
  mr.name = "new name"
220
- assert_equal({:name => "new name"}, mr.changed_attributes)
220
+ assert_equal({:name => "new name"}, mr.changed_api_attributes)
221
221
  end
222
222
 
223
223
  should 'keep track of changed arrays' do
224
224
  mr = MockResource.new(test_mock_resource)
225
- assert(mr.changed_attributes.empty?)
225
+ assert(mr.changed_api_attributes.empty?)
226
226
  mr.tarray << "new"
227
- assert_equal({:tarray => test_mock_resource[:tarray] + ["new"]}, mr.changed_attributes)
227
+ assert_equal({:tarray => test_mock_resource[:tarray] + ["new"]}, mr.changed_api_attributes)
228
228
  end
229
229
 
230
230
  should 'keep track of changed hashes' do
231
231
  mr = MockResource.new(test_mock_resource)
232
- assert(mr.changed_attributes.empty?)
232
+ assert(mr.changed_api_attributes.empty?)
233
233
  mr.thash[:some_key] = "new value"
234
- assert_equal({:thash => { :some_key => "new value" }}, mr.changed_attributes)
234
+ assert_equal({:thash => { :some_key => "new value" }}, mr.changed_api_attributes)
235
235
  end
236
236
 
237
237
  context 'constructors' do
238
238
  should 'instantiate on #new' do
239
239
  mr = MockResource.new(test_mock_resource)
240
240
  assert(mr.nested.is_a?(NestedResource))
241
+ assert(mr.nested_alt.is_a?(NestedResource))
242
+ assert(mr.nested_with.is_a?(NestedWithParent))
243
+ assert_equal(mr.path + "/nested_path", mr.nested_with.path)
241
244
  end
242
245
 
243
246
  should 'instantiate on #construct' do
244
247
  mr = MockResource.construct(test_mock_resource)
245
248
  assert(mr.nested.is_a?(NestedResource))
249
+ assert(mr.nested_alt.is_a?(NestedResource))
250
+ assert(mr.nested_with.is_a?(NestedWithParent))
251
+ assert_equal(mr.path + "/nested_path", mr.nested_with.path)
246
252
  end
247
253
 
248
254
  should 'instantiate on #refresh_from' do
249
255
  mr = MockResource.new('fake_id')
250
256
  assert(mr.nested.nil?)
251
-
252
257
  mr.refresh_from(test_mock_resource)
253
258
  assert(mr.nested.is_a?(NestedResource))
259
+ assert(mr.nested_alt.is_a?(NestedResource))
260
+ assert(mr.nested_with.is_a?(NestedWithParent))
261
+ assert_equal(mr.path + "/nested_path", mr.nested_with.path)
262
+ end
263
+
264
+ should 'with default values' do
265
+ mr = MockResource.new('fake_id')
266
+ assert(mr.nested_with.is_a?(NestedWithParent))
267
+ assert_equal(mr.path + "/nested_path", mr.nested_with.path)
254
268
  end
255
269
  end
256
270
  end
257
271
 
258
- context 'APIClass :constructor' do
272
+ context 'APIResource :constructor' do
259
273
  context 'for api_class_method' do
260
274
  setup do
261
275
  @mock.expects(:get).once.returns(test_response(test_mock_resource))
@@ -313,7 +327,7 @@ module Paid
313
327
  end
314
328
  end
315
329
 
316
- context 'APIClass api_*_method arguments' do
330
+ context 'APIResource api_*_method arguments' do
317
331
  should 'throw an ArgumentError if too few arguments are provided' do
318
332
  assert_raises(ArgumentError) { MockResource.retrieve }
319
333
  end
@@ -364,7 +378,7 @@ module Paid
364
378
  end
365
379
  end
366
380
 
367
- context 'APIClass api_instance_method paths' do
381
+ context 'APIResource api_instance_method paths' do
368
382
  should 'use the provided argument if it is present' do
369
383
  response = test_response(test_mock_resource)
370
384
  @mock.expects(:get).with("#{Paid.api_base}/custom_path", anything, anything).returns(response)
@@ -392,7 +406,7 @@ module Paid
392
406
  end
393
407
  end
394
408
 
395
- context 'APIClass api_class_method paths' do
409
+ context 'APIResource api_class_method paths' do
396
410
  should 'use the provided argument if it is present' do
397
411
  response = test_response(test_mock_resource)
398
412
  @mock.expects(:get).with("#{Paid.api_base}#{MockResource.path}/fake_id", anything, anything).returns(response)
@@ -3,7 +3,7 @@ require File.expand_path('../../test_helper', __FILE__)
3
3
  module Paid
4
4
  class AccountTest < Test::Unit::TestCase
5
5
  setup do
6
- @account_url = "#{Paid.api_base}/v0/account"
6
+ @account_url = "#{Paid.api_base}/account"
7
7
  end
8
8
 
9
9
  should 'be retrievable' do
@@ -41,8 +41,8 @@ module Paid
41
41
  end
42
42
 
43
43
  should 'be registered' do
44
- assert(APIClass.subclasses.include?(Paid::Account))
45
- assert_equal(Paid::Account, APIClass.subclass_fetch("account"))
44
+ assert(APIResource.api_subclasses.include?(Paid::Account))
45
+ assert_equal(Paid::Account, APIResource.api_subclass_fetch("account"))
46
46
  end
47
47
 
48
48
  end
@@ -1,16 +1,22 @@
1
1
  require File.expand_path('../../test_helper', __FILE__)
2
2
 
3
3
  module Paid
4
- class ApiListTest < Test::Unit::TestCase
4
+ class APIListTest < ::Test::Unit::TestCase
5
5
 
6
- should 'have an object attribute' do
7
- assert(Paid::APIList.method_defined?(:object))
8
- assert(Paid::APIList.method_defined?(:object=))
9
- end
6
+ context '#new / #initialize' do
7
+ setup do
8
+ @fake_resource = {:data => "fake-data"}
9
+ @list = APIList.new(APIResource, [@fake_resource])
10
+ end
11
+
12
+ should 'set the klass' do
13
+ assert_equal(APIResource, @list.klass)
14
+ end
10
15
 
11
- should 'have an data attribute' do
12
- assert(Paid::APIList.method_defined?(:data))
13
- assert(Paid::APIList.method_defined?(:data=))
16
+ should 'convert the data to klass instances' do
17
+ assert(@list.first.is_a?(APIResource))
18
+ assert_equal(@fake_resource, @list.first.json)
19
+ end
14
20
  end
15
21
 
16
22
  end
@@ -0,0 +1,89 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ module Paid
4
+ class APIMethodTest < ::Test::Unit::TestCase
5
+ setup do
6
+ @method = :get
7
+ @path = "/testing"
8
+ @params = { :param_a => "1" }
9
+ @headers = { :header_a => "a" }
10
+ @object = mock
11
+ @api_method = APIMethod.new(@method, @path, @params, @headers, @object)
12
+ end
13
+
14
+ context '#new / #initialize' do
15
+ should 'set the api_key' do
16
+ assert_equal(Paid.api_key, @api_method.api_key)
17
+ end
18
+
19
+ should 'set the api_base' do
20
+ assert_equal(Paid.api_base, @api_method.api_base)
21
+ end
22
+
23
+ should 'use PathBuilder with path, object, and params' do
24
+ PathBuilder.expects(:build).with(@path, @object, @params).returns(@path)
25
+ APIMethod.new(@method, @path, @params, @headers, @object)
26
+ end
27
+
28
+ should 'use ParamsBuilder with params' do
29
+ ParamsBuilder.expects(:build).with(@params).returns(@params)
30
+ APIMethod.new(@method, @path, @params, @headers, @object)
31
+ end
32
+
33
+ should 'use HeadersBuilder with headers, api_key, and nil auth_header' do
34
+ HeadersBuilder.expects(:build).with(@headers, Paid.api_key, nil).returns(@headers)
35
+ APIMethod.new(@method, @path, @params, @headers, @object)
36
+ end
37
+
38
+ should 'verify the api key exists' do
39
+ Paid.api_key = nil
40
+ assert_raises(AuthenticationError) do
41
+ APIMethod.new(@method, @path, @params, @headers, @object)
42
+ end
43
+ end
44
+ end
45
+
46
+ context '#execute' do
47
+ setup do
48
+ @mock_response = mock
49
+ @mock_response.stubs(:body).returns('{"status": "success"}')
50
+ @mock_response.stubs(:code).returns(200)
51
+ end
52
+
53
+ should 'call Requester.request with the set attrs' do
54
+ Requester.expects(:request).with(@method, @api_method.url, @api_method.params, @api_method.headers).returns(@mock_response)
55
+ @api_method.execute
56
+ end
57
+
58
+ should 'create an APIError if the request fails' do
59
+ Requester.expects(:request).raises(RestClient::RequestTimeout.new)
60
+
61
+ assert_raises(APIError) { @api_method.execute }
62
+ end
63
+
64
+ should 'return the response parsed as json' do
65
+ Requester.expects(:request).returns(@mock_response)
66
+ assert_equal({:status => "success"}, @api_method.execute)
67
+ end
68
+
69
+ should 'return an AuthenticationError if the status is 401' do
70
+ error = RestClient::ExceptionWithResponse.new
71
+ error.expects(:http_code).returns(401)
72
+
73
+ Requester.expects(:request).raises(error)
74
+ assert_raises(AuthenticationError) { @api_method.execute }
75
+ end
76
+ end
77
+
78
+ context '#response_json' do
79
+ setup do
80
+ @api_method.response_body = 'not-valid-json'
81
+ end
82
+
83
+ should 'throw an error if the response_body isnt valid json' do
84
+ assert_raises(APIError) { @api_method.response_json }
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -3,7 +3,7 @@ require File.expand_path('../../test_helper', __FILE__)
3
3
  module Paid
4
4
  class CustomerTest < Test::Unit::TestCase
5
5
  setup do
6
- @customer_url = "#{Paid.api_base}/v0/customers"
6
+ @customer_url = "#{Paid.api_base}/customers"
7
7
  end
8
8
 
9
9
  context 'Customer class' do
@@ -16,7 +16,10 @@ module Paid
16
16
 
17
17
  should 'be retrieveable by external_id' do
18
18
  external_id = "external_id_for_cust"
19
- @mock.expects(:get).once.with("#{@customer_url}/by_external_id/#{external_id}", anything, anything).returns(test_response(test_customer))
19
+ @mock.expects(:get).once.with do |url, headers, params|
20
+ Regexp.new("/customers/by_external_id").match(url) &&
21
+ params[:external_id] == external_id
22
+ end.returns(test_response(test_customer))
20
23
  customer = Paid::Customer.by_external_id(external_id)
21
24
  assert(customer.is_a?(Paid::Customer))
22
25
  end
@@ -54,7 +57,7 @@ module Paid
54
57
  customer.email = "new_email@domain.com"
55
58
 
56
59
  @mock.expects(:put).once.with do |url, headers, params|
57
- params == customer.changed_attributes && url == "#{@customer_url}/#{customer.id}"
60
+ !params.nil? && url == "#{@customer_url}/#{customer.id}"
58
61
  end.returns(test_response(test_customer))
59
62
 
60
63
  # This should update this instance with test_customer since it was returned
@@ -65,7 +68,9 @@ module Paid
65
68
 
66
69
  should 'be able to generate an invoice' do
67
70
  customer = Paid::Customer.new(test_customer)
68
- @mock.expects(:post).once.with("#{@customer_url}/#{customer.id}/generate_invoice", anything, anything).returns(test_response(test_invoice))
71
+ @mock.expects(:post).once.with do |url, headers, params|
72
+ Regexp.new("/customers/#{customer.id}/generate_invoice").match(url)
73
+ end.returns(test_response(test_invoice))
69
74
 
70
75
  invoice = customer.generate_invoice
71
76
  assert(invoice.is_a?(Paid::Invoice))
@@ -73,7 +78,10 @@ module Paid
73
78
 
74
79
  should 'be able to list invoices' do
75
80
  customer = Paid::Customer.new(test_customer)
76
- @mock.expects(:get).once.with("#{Paid.api_base}#{Paid::Invoice.path}?customer=#{customer.id}", anything, anything).returns(test_response(test_invoice_list))
81
+ @mock.expects(:get).once.with do |url, headers, params|
82
+ Regexp.new("/invoices").match(url) &&
83
+ params[:customer] == customer.id
84
+ end.returns(test_response(test_invoice_list))
77
85
 
78
86
  invoices = customer.invoices
79
87
  assert(invoices.is_a?(Paid::APIList))
@@ -84,7 +92,10 @@ module Paid
84
92
 
85
93
  should 'be able to list transactions' do
86
94
  customer = Paid::Customer.new(test_customer)
87
- @mock.expects(:get).once.with("#{Paid.api_base}#{Paid::Transaction.path}?customer=#{customer.id}", anything, anything).returns(test_response(test_transaction_list))
95
+ @mock.expects(:get).once.with do |url, headers, params|
96
+ Regexp.new("/transactions").match(url) &&
97
+ params[:customer] == customer.id
98
+ end.returns(test_response(test_transaction_list))
88
99
 
89
100
  transactions = customer.transactions
90
101
  assert(transactions.is_a?(Paid::APIList))
@@ -97,8 +108,7 @@ module Paid
97
108
 
98
109
  context 'Retrieved Paid::Customer instance' do
99
110
  setup do
100
- @mock.expects(:get).once.returns(test_response(test_customer))
101
- @customer = Paid::Customer.retrieve('customer_id')
111
+ @customer = Paid::Customer.new(test_customer)
102
112
  end
103
113
 
104
114
  should 'have the id attribute' do
@@ -184,8 +194,8 @@ module Paid
184
194
  end
185
195
 
186
196
  should 'be registered' do
187
- assert(APIClass.subclasses.include?(Paid::Customer))
188
- assert_equal(Paid::Customer, APIClass.subclass_fetch("customer"))
197
+ assert(APIResource.api_subclasses.include?(Paid::Customer))
198
+ assert_equal(Paid::Customer, APIResource.api_subclass_fetch("customer"))
189
199
  end
190
200
 
191
201
  end
@@ -3,7 +3,7 @@ require File.expand_path('../../test_helper', __FILE__)
3
3
  module Paid
4
4
  class EventTest < Test::Unit::TestCase
5
5
  setup do
6
- @event_url = "#{Paid.api_base}/v0/events"
6
+ @event_url = "#{Paid.api_base}/events"
7
7
  end
8
8
 
9
9
  context 'Event class' do
@@ -58,7 +58,6 @@ module Paid
58
58
  end
59
59
 
60
60
  should 'have & convert the data attribute' do
61
- assert(@event.data.is_a?(Paid::APIClass))
62
61
  event2 = Paid::Event.new(test_event(test_invoice))
63
62
  assert(event2.data.is_a?(Paid::Invoice))
64
63
  end
@@ -66,8 +65,8 @@ module Paid
66
65
  end
67
66
 
68
67
  should 'be registered' do
69
- assert(APIClass.subclasses.include?(Paid::Event))
70
- assert_equal(Paid::Event, APIClass.subclass_fetch("event"))
68
+ assert(APIResource.api_subclasses.include?(Paid::Event))
69
+ assert_equal(Paid::Event, APIResource.api_subclass_fetch("event"))
71
70
  end
72
71
 
73
72
  end