nurego 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +59 -0
  3. data/LICENSE +32 -0
  4. data/README.md +25 -0
  5. data/Rakefile +41 -0
  6. data/VERSION +1 -0
  7. data/examples/enumerate_plans.rb +17 -0
  8. data/lib/data/ca-certificates.crt +53 -0
  9. data/lib/nurego.rb +287 -0
  10. data/lib/nurego/api_operations/create.rb +16 -0
  11. data/lib/nurego/api_operations/delete.rb +11 -0
  12. data/lib/nurego/api_operations/list.rb +16 -0
  13. data/lib/nurego/api_operations/update.rb +37 -0
  14. data/lib/nurego/api_resource.rb +33 -0
  15. data/lib/nurego/auth.rb +112 -0
  16. data/lib/nurego/bill.rb +6 -0
  17. data/lib/nurego/connector.rb +7 -0
  18. data/lib/nurego/customer.rb +29 -0
  19. data/lib/nurego/entitlement.rb +27 -0
  20. data/lib/nurego/errors/api_connection_error.rb +4 -0
  21. data/lib/nurego/errors/api_error.rb +4 -0
  22. data/lib/nurego/errors/authentication_error.rb +4 -0
  23. data/lib/nurego/errors/card_error.rb +10 -0
  24. data/lib/nurego/errors/invalid_request_error.rb +10 -0
  25. data/lib/nurego/errors/nurego_error.rb +24 -0
  26. data/lib/nurego/errors/user_not_found_error.rb +4 -0
  27. data/lib/nurego/feature.rb +5 -0
  28. data/lib/nurego/instance.rb +9 -0
  29. data/lib/nurego/json.rb +21 -0
  30. data/lib/nurego/list_object.rb +35 -0
  31. data/lib/nurego/nurego_object.rb +167 -0
  32. data/lib/nurego/offering.rb +17 -0
  33. data/lib/nurego/organization.rb +23 -0
  34. data/lib/nurego/password_reset.rb +14 -0
  35. data/lib/nurego/payment_method.rb +16 -0
  36. data/lib/nurego/plan.rb +9 -0
  37. data/lib/nurego/registration.rb +18 -0
  38. data/lib/nurego/util.rb +109 -0
  39. data/lib/nurego/version.rb +3 -0
  40. data/nurego.gemspec +31 -0
  41. data/spec/unit/nurego/api_resource_spec.rb +326 -0
  42. data/spec/unit/nurego/customer_spec.rb +75 -0
  43. data/spec/unit/nurego/list_object_spec.rb +14 -0
  44. data/spec/unit/nurego/util_spec.rb +27 -0
  45. data/spec/unit/test_helper.rb +94 -0
  46. metadata +279 -0
@@ -0,0 +1,167 @@
1
+ module Nurego
2
+ class NuregoObject
3
+ include Enumerable
4
+
5
+ attr_accessor :api_key
6
+ @@permanent_attributes = Set.new([:api_key, :id])
7
+
8
+ # The default :id method is deprecated and isn't useful to us
9
+ if method_defined?(:id)
10
+ undef :id
11
+ end
12
+
13
+ def initialize(id=nil, api_key=nil)
14
+ # parameter overloading!
15
+ if id.kind_of?(Hash)
16
+ @retrieve_options = id.dup
17
+ @retrieve_options.delete(:id)
18
+ id = id[:id]
19
+ else
20
+ @retrieve_options = {}
21
+ end
22
+
23
+ @api_key = api_key
24
+ @values = {}
25
+ # This really belongs in APIResource, but not putting it there allows us
26
+ # to have a unified inspect method
27
+ @unsaved_values = Set.new
28
+ @transient_values = Set.new
29
+ @values[:id] = id if id
30
+ end
31
+
32
+ def self.construct_from(values, api_key=nil)
33
+ obj = self.new(values[:id], api_key)
34
+ obj.refresh_from(values, api_key)
35
+ obj
36
+ end
37
+
38
+ def to_s(*args)
39
+ Nurego::JSON.dump(@values, :pretty => true)
40
+ end
41
+
42
+ def inspect
43
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
44
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + Nurego::JSON.dump(@values, :pretty => true)
45
+ end
46
+
47
+ def refresh_from(values, api_key, partial=false)
48
+ @api_key = api_key
49
+
50
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
51
+ added = Set.new(values.keys - @values.keys)
52
+ # Wipe old state before setting new. This is useful for e.g. updating a
53
+ # customer, where there is no persistent card parameter. Mark those values
54
+ # which don't persist as transient
55
+
56
+ instance_eval do
57
+ remove_accessors(removed)
58
+ add_accessors(added)
59
+ end
60
+ removed.each do |k|
61
+ @values.delete(k)
62
+ @transient_values.add(k)
63
+ @unsaved_values.delete(k)
64
+ end
65
+ values.each do |k, v|
66
+ @values[k] = Util.convert_to_nurego_object(v, api_key)
67
+ @transient_values.delete(k)
68
+ @unsaved_values.delete(k)
69
+ end
70
+ end
71
+
72
+ def [](k)
73
+ @values[k.to_sym]
74
+ end
75
+
76
+ def []=(k, v)
77
+ send(:"#{k}=", v)
78
+ end
79
+
80
+ def keys
81
+ @values.keys
82
+ end
83
+
84
+ def values
85
+ @values.values
86
+ end
87
+
88
+ def to_json(*a)
89
+ Nurego::JSON.dump(@values)
90
+ end
91
+
92
+ def as_json(*a)
93
+ @values.as_json(*a)
94
+ end
95
+
96
+ def to_hash
97
+ @values
98
+ end
99
+
100
+ def each(&blk)
101
+ @values.each(&blk)
102
+ end
103
+
104
+ protected
105
+
106
+ def metaclass
107
+ class << self; self; end
108
+ end
109
+
110
+ def remove_accessors(keys)
111
+ metaclass.instance_eval do
112
+ keys.each do |k|
113
+ next if @@permanent_attributes.include?(k)
114
+ k_eq = :"#{k}="
115
+ remove_method(k) if method_defined?(k)
116
+ remove_method(k_eq) if method_defined?(k_eq)
117
+ end
118
+ end
119
+ end
120
+
121
+ def add_accessors(keys)
122
+ metaclass.instance_eval do
123
+ keys.each do |k|
124
+ next if @@permanent_attributes.include?(k)
125
+ k_eq = :"#{k}="
126
+ define_method(k) { @values[k] }
127
+ define_method(k_eq) do |v|
128
+ if v == ""
129
+ raise ArgumentError.new(
130
+ "You cannot set #{k} to an empty string." +
131
+ "We interpret empty strings as nil in requests." +
132
+ "You may set #{self}.#{k} = nil to delete the property.")
133
+ end
134
+ @values[k] = v
135
+ @unsaved_values.add(k)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def method_missing(name, *args)
142
+ # TODO: only allow setting in updateable classes.
143
+ if name.to_s.end_with?('=')
144
+ attr = name.to_s[0...-1].to_sym
145
+ add_accessors([attr])
146
+ begin
147
+ mth = method(name)
148
+ rescue NameError
149
+ raise NoMethodError.new("Cannot set #{attr} on this object. HINT: you can't set: #{@@permanent_attributes.to_a.join(', ')}")
150
+ end
151
+ return mth.call(args[0])
152
+ else
153
+ return @values[name] if @values.has_key?(name)
154
+ end
155
+
156
+ begin
157
+ super
158
+ rescue NoMethodError => e
159
+ if @transient_values.include?(name)
160
+ raise NoMethodError.new(e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Nurego's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}")
161
+ else
162
+ raise
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,17 @@
1
+ module Nurego
2
+ class Offering < APIResource
3
+
4
+ def self.retrieve(id, api_key=nil)
5
+ raise NotImplementedError.new("Offering cannot be retrieved with ID. Retrieve an offering using Offering.current")
6
+ end
7
+
8
+ def self.current(params = {}, api_key = nil)
9
+ response, api_key = Nurego.request(:get, self.url, api_key, params)
10
+ Util.convert_to_nurego_object(response, api_key)
11
+ end
12
+
13
+ def plans
14
+ Plan.all({:offering => id }, @api_key)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Nurego
2
+ class Organization < APIResource
3
+ include Nurego::APIOperations::List
4
+ include Nurego::APIOperations::Update
5
+
6
+ def instances
7
+ Instance.all({:organization => id }, @api_key)
8
+ end
9
+
10
+ def paymentmethod
11
+ PaymentMethod.all({:organization => id}, @api_key)
12
+ end
13
+
14
+ def bills
15
+ Bill.all({ :organization => id }, @api_key)[:bills]
16
+ end
17
+
18
+ def entitlements(feature_id = nil)
19
+ Entitlement.all({:organization => id, :feature_id => feature_id}, @api_key)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Nurego
2
+ class PasswordReset < APIResource
3
+ include Nurego::APIOperations::Create
4
+ include Nurego::APIOperations::Delete
5
+ include Nurego::APIOperations::List
6
+
7
+ include Nurego::Auth
8
+
9
+ def complete(params)
10
+ Nurego::Auth.change_password(self.customer_id, params[:password], params[:current_password])
11
+ self
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Nurego
2
+ class PaymentMethod < APIResource
3
+ include Nurego::APIOperations::Create
4
+ include Nurego::APIOperations::Update
5
+ include Nurego::APIOperations::List
6
+
7
+ def url
8
+ PaymentMethod.url
9
+ end
10
+
11
+ def self.retrieve(id, api_key = nil)
12
+ raise NotImplementedError.new("Payment Method cannot be retrieved without a organization ID. Retrieve a paymentmethod using organization.paymentmethod")
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Nurego
2
+ class Plan < APIResource
3
+ include Nurego::APIOperations::List
4
+
5
+ def features
6
+ Features.all({:plan => id }, @api_key)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Nurego
2
+ class Registration < APIResource
3
+ include Nurego::APIOperations::Create
4
+ include Nurego::APIOperations::List
5
+
6
+ def complete(params)
7
+ response, api_key = Nurego.request(:post, complete_url, @api_key, params)
8
+ refresh_from({customer: response}, api_key, true)
9
+ customer
10
+ end
11
+
12
+ private
13
+
14
+ def complete_url
15
+ url + '/complete'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,109 @@
1
+ module Nurego
2
+ module Util
3
+ def self.objects_to_ids(h)
4
+ case h
5
+ when APIResource
6
+ h.id
7
+ when Hash
8
+ res = {}
9
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
10
+ res
11
+ when Array
12
+ h.map { |v| objects_to_ids(v) }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def self.object_classes
19
+ @object_classes ||= {
20
+ 'customer' => Customer,
21
+ 'registration' => Registration,
22
+ 'organization' => Organization,
23
+ 'instance' => Instance,
24
+ 'connector' => Connector,
25
+ 'passwordreset' => PasswordReset,
26
+ 'offering' => Offering,
27
+ 'plan' => Plan,
28
+ 'feature' => Feature,
29
+ 'paymentmethod' => PaymentMethod,
30
+ 'bill' => Bill,
31
+ 'list' => ListObject
32
+ }
33
+ end
34
+
35
+ def self.convert_to_nurego_object(resp, api_key)
36
+ case resp
37
+ when Array
38
+ resp.map { |i| convert_to_nurego_object(i, api_key) }
39
+ when Hash
40
+ # Try converting to a known object class. If none available, fall back to generic NuregoObject
41
+ object_classes.fetch(resp[:object], NuregoObject).construct_from(resp, api_key)
42
+ else
43
+ resp
44
+ end
45
+ end
46
+
47
+ def self.file_readable(file)
48
+ # This is nominally equivalent to File.readable?, but that can
49
+ # report incorrect results on some more oddball filesystems
50
+ # (such as AFS)
51
+ begin
52
+ File.open(file) { |f| }
53
+ rescue
54
+ false
55
+ else
56
+ true
57
+ end
58
+ end
59
+
60
+ def self.symbolize_names(object)
61
+ case object
62
+ when Hash
63
+ new = {}
64
+ object.each do |key, value|
65
+ key = (key.to_sym rescue key) || key
66
+ new[key] = symbolize_names(value)
67
+ end
68
+ new
69
+ when Array
70
+ object.map { |value| symbolize_names(value) }
71
+ else
72
+ object
73
+ end
74
+ end
75
+
76
+ def self.url_encode(key)
77
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
78
+ end
79
+
80
+ def self.flatten_params(params, parent_key=nil)
81
+ result = []
82
+ params.each do |key, value|
83
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
84
+ if value.is_a?(Hash)
85
+ result += flatten_params(value, calculated_key)
86
+ elsif value.is_a?(Array)
87
+ result += flatten_params_array(value, calculated_key)
88
+ else
89
+ result << [calculated_key, value]
90
+ end
91
+ end
92
+ result
93
+ end
94
+
95
+ def self.flatten_params_array(value, calculated_key)
96
+ result = []
97
+ value.each do |elem|
98
+ if elem.is_a?(Hash)
99
+ result += flatten_params(elem, calculated_key)
100
+ elsif elem.is_a?(Array)
101
+ result += flatten_params_array(elem, calculated_key)
102
+ else
103
+ result << ["#{calculated_key}[]", elem]
104
+ end
105
+ end
106
+ result
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module Nurego
2
+ VERSION = '1.0.1'
3
+ end
data/nurego.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'nurego/version'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = 'nurego'
7
+ s.version = Nurego::VERSION
8
+ s.summary = 'Ruby bindings for the Nurego API'
9
+ s.description = 'Business Operations for Subscription Services. See http://www.nurego.com for details.'
10
+ s.authors = ['Ilia Gilderman']
11
+ s.email = ['ilia@nurego.com']
12
+ s.homepage = 'http://www.nurego.com/api'
13
+ s.license = 'MIT'
14
+
15
+ s.add_dependency('rest-client', '~> 1.4')
16
+ s.add_dependency('mime-types', '~> 1.25')
17
+ s.add_dependency('multi_json', '>= 1.0.4', '< 2')
18
+ s.add_dependency('cf-uaa-lib', '= 1.3.10')
19
+
20
+ s.add_development_dependency('rake')
21
+ s.add_development_dependency('uuidtools')
22
+ s.add_development_dependency('rspec')
23
+ s.add_development_dependency('simplecov')
24
+ s.add_development_dependency('simplecov-rcov')
25
+ s.add_development_dependency('rack-test')
26
+ s.add_development_dependency('ci_reporter')
27
+
28
+ s.files = Dir.glob("{lib,examples}/**/*") + %w(Gemfile Gemfile.lock nurego.gemspec Rakefile VERSION LICENSE README.md)
29
+ s.test_files = `git ls-files -- spec/unit*`.split("\n")
30
+ s.require_paths = ['lib']
31
+ end
@@ -0,0 +1,326 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path('../../test_helper', __FILE__)
3
+
4
+ describe "Nurego::ApiResource" do
5
+ before(:each) do
6
+ @mock = double
7
+ Nurego.mock_rest_client = @mock
8
+ Nurego.api_key="foo"
9
+ end
10
+
11
+ it "creating a new APIResource should not fetch over the network" do
12
+ @mock.should_not_receive(:get)
13
+ Nurego::Customer.new("someid")
14
+ end
15
+
16
+ it "creating a new APIResource from a hash should not fetch over the network" do
17
+ @mock.should_not_receive(:get)
18
+ Nurego::Customer.construct_from({
19
+ :id => "somecustomer",
20
+ :card => {:id => "somecard", :object => "card"},
21
+ :object => "customer"
22
+ })
23
+ end
24
+
25
+ it "setting an attribute should not cause a network request" do
26
+ @mock.should_not_receive(:get)
27
+ @mock.should_not_receive(:post)
28
+ c = Nurego::Customer.new("test_customer");
29
+ c.card = {:id => "somecard", :object => "card"}
30
+ end
31
+
32
+ it "accessing id should not issue a fetch" do
33
+ @mock.should_not_receive(:get)
34
+ c = Nurego::Customer.new("test_customer");
35
+ c.id
36
+ end
37
+
38
+ it "not specifying api credentials should raise an exception" do
39
+ Nurego.api_key = nil
40
+ expect { Nurego::Customer.new("test_customer").refresh }.to raise_error(Nurego::AuthenticationError)
41
+ end
42
+
43
+ it "specifying api credentials containing whitespace should raise an exception" do
44
+ Nurego.api_key = "key "
45
+ expect { Nurego::Customer.new("test_customer").refresh }.to raise_error(Nurego::AuthenticationError)
46
+ end
47
+
48
+ it "specifying invalid api credentials should raise an exception" do
49
+ Nurego.api_key = "invalid"
50
+ response = test_response(test_invalid_api_key_error, 401)
51
+ expect do
52
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 401))
53
+ Nurego::Customer.retrieve("failing_customer")
54
+ end.to raise_error(Nurego::AuthenticationError)
55
+ end
56
+
57
+ it "AuthenticationErrors should have an http status, http body, and JSON body" do
58
+ Nurego.api_key = "invalid"
59
+ response = test_response(test_invalid_api_key_error, 401)
60
+ begin
61
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 401))
62
+ Nurego::Customer.retrieve("failing_customer")
63
+ rescue Nurego::AuthenticationError => e
64
+ e.http_status.should eq(401)
65
+ !!e.http_body.should(be_true)
66
+ !!e.json_body[:error][:message].should(be_true)
67
+ test_invalid_api_key_error['error']['message'].should eq(e.json_body[:error][:message])
68
+ end
69
+ end
70
+
71
+ context "when specifying per-object credentials" do
72
+ context "with no global API key set" do
73
+ xit "use the per-object credential when creating" do
74
+ Nurego.should_receive(:execute_request).with do |opts|
75
+ opts[:headers][:authorization] == 'Bearer sk_test_local'
76
+ end.and_return(test_response(test_charge))
77
+
78
+ Nurego::Charge.create({:card => {:number => '4242424242424242'}},
79
+ 'sk_test_local')
80
+ end
81
+ end
82
+
83
+ context "with a global API key set" do
84
+ before(:each) do
85
+ Nurego.api_key = "global"
86
+ end
87
+
88
+ xit "use the per-object credential when creating" do
89
+ Nurego.should_receive(:execute_request).with do |opts|
90
+ opts[:headers][:authorization] == 'Bearer local'
91
+ end.and_return(test_response(test_charge))
92
+
93
+ Nurego::Charge.create({:card => {:number => '4242424242424242'}},
94
+ 'local')
95
+ end
96
+
97
+ xit "use the per-object credential when retrieving and making other calls" do
98
+ Nurego.should_receive(:execute_request).with do |opts|
99
+ opts[:url] == "#{Nurego.api_base}/v1/charges/ch_test_charge" &&
100
+ opts[:headers][:authorization] == 'Bearer local'
101
+ end.and_return(test_response(test_charge))
102
+ Nurego.should_receive(:execute_request).with do |opts|
103
+ opts[:url] == "#{Nurego.api_base}/v1/charges/ch_test_charge/refund" &&
104
+ opts[:headers][:authorization] == 'Bearer local'
105
+ end.and_return(test_response(test_charge))
106
+
107
+ ch = Nurego::Charge.retrieve('ch_test_charge', 'local')
108
+ ch.refund
109
+ end
110
+ end
111
+ end
112
+
113
+ context "with valid credentials" do
114
+ xit "urlencode values in GET params" do
115
+ response = test_response(test_charge_array)
116
+ @mock.should_receive(:get).with("#{Nurego.api_base}/v1/charges?customer=test%20customer", nil, nil).and_return(response)
117
+ charges = Nurego::Charge.all(:customer => 'test customer').data
118
+ assert charges.kind_of? Array
119
+ end
120
+
121
+ xit "construct URL properly with base query parameters" do
122
+ response = test_response(test_invoice_customer_array)
123
+ @mock.should_receive(:get).with("#{Nurego.api_base}/v1/invoices?customer=test_customer", nil, nil).and_return(response)
124
+ invoices = Nurego::Invoice.all(:customer => 'test_customer')
125
+
126
+ @mock.should_receive(:get).with("#{Nurego.api_base}/v1/invoices?customer=test_customer&paid=true", nil, nil).and_return(response)
127
+ invoices.all(:paid => true)
128
+ end
129
+
130
+ it "a 400 should give an InvalidRequestError with http status, body, and JSON body" do
131
+ response = test_response(test_missing_id_error, 400)
132
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 404))
133
+ begin
134
+ Nurego::Customer.retrieve("foo")
135
+ rescue Nurego::InvalidRequestError => e
136
+ e.http_status.should eq(400)
137
+ !!e.http_body.should(be_true)
138
+ e.json_body.kind_of?(Hash).should be_true
139
+ end
140
+ end
141
+
142
+ it "a 401 should give an AuthenticationError with http status, body, and JSON body" do
143
+ response = test_response(test_missing_id_error, 401)
144
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 404))
145
+ begin
146
+ Nurego::Customer.retrieve("foo")
147
+ rescue Nurego::AuthenticationError => e
148
+ e.http_status.should eq(401)
149
+ !!e.http_body.should(be_true)
150
+ e.json_body.kind_of?(Hash).should be_true
151
+ end
152
+ end
153
+
154
+ it "a 402 should give a CardError with http status, body, and JSON body" do
155
+ response = test_response(test_missing_id_error, 402)
156
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 404))
157
+ begin
158
+ Nurego::Customer.retrieve("foo")
159
+ rescue Nurego::CardError => e
160
+ e.http_status.should eq(402)
161
+ !!e.http_body.should(be_true)
162
+ e.json_body.kind_of?(Hash).should be_true
163
+ end
164
+ end
165
+
166
+ it "a 404 should give an InvalidRequestError with http status, body, and JSON body" do
167
+ response = test_response(test_missing_id_error, 404)
168
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 404))
169
+ begin
170
+ Nurego::Customer.retrieve("foo")
171
+ rescue Nurego::InvalidRequestError => e
172
+ e.http_status.should eq(404)
173
+ !!e.http_body.should(be_true)
174
+ e.json_body.kind_of?(Hash).should be_true
175
+ end
176
+ end
177
+
178
+ xit "setting a nil value for a param should exclude that param from the request" do
179
+ @mock.should_receive(:get).with do |url, api_key, params|
180
+ uri = URI(url)
181
+ query = CGI.parse(uri.query)
182
+ (url =~ %r{^#{Nurego.api_base}/v1/charges?} &&
183
+ query.keys.sort == ['offset', 'sad'])
184
+ end.and_return(test_response({ :count => 1, :data => [test_charge] }))
185
+ Nurego::Charge.all(:count => nil, :offset => 5, :sad => false)
186
+
187
+ @mock.should_receive(:post).with do |url, api_key, params|
188
+ url == "#{Nurego.api_base}/v1/charges" &&
189
+ api_key.nil? &&
190
+ CGI.parse(params) == { 'amount' => ['50'], 'currency' => ['usd'] }
191
+ end.and_return(test_response({ :count => 1, :data => [test_charge] }))
192
+ Nurego::Charge.create(:amount => 50, :currency => 'usd', :card => { :number => nil })
193
+ end
194
+
195
+ it "requesting with a unicode ID should result in a request" do
196
+ response = test_response(test_missing_id_error, 404)
197
+ @mock.should_receive(:get).with("#{Nurego.api_base}/v1/customers/%E2%98%83", nil, nil).and_raise(RestClient::ExceptionWithResponse.new(response, 404))
198
+ c = Nurego::Customer.new("☃")
199
+ expect { c.refresh }.to raise_error(Nurego::InvalidRequestError)
200
+ end
201
+
202
+ it "requesting with no ID should result in an InvalidRequestError with no request" do
203
+ c = Nurego::Customer.new
204
+ expect { c.refresh }.to raise_error(Nurego::InvalidRequestError)
205
+ end
206
+
207
+ xit "making a GET request with parameters should have a query string and no body" do
208
+ params = { :limit => 1 }
209
+ @mock.should_receive(:get).with("#{Nurego.api_base}/v1/charges?limit=1", nil, nil).and_return(test_response([test_charge]))
210
+ Nurego::Charge.all(params)
211
+ end
212
+
213
+ xit "making a POST request with parameters should have a body and no query string" do
214
+ params = { :amount => 100, :currency => 'usd', :card => 'sc_token' }
215
+ @mock.should_receive(:post).once.with do |url, get, post|
216
+ get.nil? && CGI.parse(post) == {'amount' => ['100'], 'currency' => ['usd'], 'card' => ['sc_token']}
217
+ end.and_return(test_response(test_charge))
218
+ Nurego::Charge.create(params)
219
+ end
220
+
221
+ it "loading an object should issue a GET request" do
222
+ @mock.should_receive(:get).once.and_return(test_response(test_customer))
223
+ c = Nurego::Customer.new("test_customer")
224
+ c.refresh
225
+ end
226
+
227
+ xit "using array accessors should be the same as the method interface" do
228
+ @mock.should_receive(:get).once.and_return(test_response(test_customer))
229
+ c = Nurego::Customer.new("test_customer")
230
+ c.refresh
231
+ c.created.should eq(c[:created])
232
+ c.created.should eq(c['created'])
233
+ c['created'] = 12345
234
+ c.created.should eq(12345)
235
+ end
236
+
237
+ xit "accessing a property other than id or parent on an unfetched object should fetch it" do
238
+ @mock.should_receive(:get).once.and_return(test_response(test_customer))
239
+ c = Nurego::Customer.new("test_customer")
240
+ c.charges
241
+ end
242
+
243
+ xit "updating an object should issue a POST request with only the changed properties" do
244
+ @mock.should_receive(:post).with do |url, api_key, params|
245
+ url == "#{Nurego.api_base}/v1/customers/c_test_customer" && api_key.nil? && CGI.parse(params) == {'description' => ['another_mn']}
246
+ end.once.returns(test_response(test_customer))
247
+ c = Nurego::Customer.construct_from(test_customer)
248
+ c.description = "another_mn"
249
+ c.save
250
+ end
251
+
252
+ xit "updating should merge in returned properties" do
253
+ @mock.should_receive(:post).once.and_return(test_response(test_customer))
254
+ c = Nurego::Customer.new("c_test_customer")
255
+ c.description = "another_mn"
256
+ c.save
257
+ assert_equal false, c.livemode
258
+ end
259
+
260
+ xit "deleting should send no props and result in an object that has no props other deleted" do
261
+ @mock.should_not_receive(:get)
262
+ @mock.should_not_receive(:post)
263
+ @mock.should_receive(:delete).with("#{Nurego.api_base}/v1/customers/c_test_customer", nil, nil).once.and_return(test_response({ "id" => "test_customer", "deleted" => true }))
264
+
265
+ c = Nurego::Customer.construct_from(test_customer)
266
+ c.delete
267
+ c.deleted.should be_true
268
+
269
+ assert_raises NoMethodError do
270
+ c.livemode
271
+ end
272
+ end
273
+
274
+ xit "loading an object with properties that have specific types should instantiate those classes" do
275
+ @mock.should_receive(:get).once.and_return(test_response(test_charge))
276
+ c = Nurego::Charge.retrieve("test_charge")
277
+ (c.card.kind_of?(Nurego::NuregoObject) && c.card.object == 'card').should be_true
278
+ end
279
+
280
+ xit "loading all of an APIResource should return an array of recursively instantiated objects" do
281
+ @mock.should_receive(:get).once.and_return(test_response(test_charge_array))
282
+ c = Nurego::Charge.all.data
283
+ c.kind_of?(Array).should be_true
284
+ c[0].kind_of?(Nurego::Charge).should be_true
285
+ (c[0].card.kind_of?(Nurego::NuregoObject) && c[0].card.object == 'card').should be_true
286
+ end
287
+
288
+ context "error checking" do
289
+
290
+ it "404s should raise an InvalidRequestError" do
291
+ response = test_response(test_missing_id_error, 404)
292
+ @mock.should_receive(:get).and_raise(RestClient::ExceptionWithResponse.new(response, 404))
293
+
294
+ rescued = false
295
+ begin
296
+ Nurego::Customer.new("test_customer").refresh
297
+ assert false #shouldn't get here either
298
+ rescue Nurego::InvalidRequestError => e # we don't use assert_raises because we want to examine e
299
+ rescued = true
300
+ e.kind_of?(Nurego::InvalidRequestError).should be_true
301
+ e.param.should eq("id")
302
+ e.message.should eq("Missing id")
303
+ end
304
+
305
+ rescued.should be_true
306
+ end
307
+
308
+ it "5XXs should raise an APIError" do
309
+ response = test_response(test_api_error, 500)
310
+ @mock.should_receive(:get).once.and_raise(RestClient::ExceptionWithResponse.new(response, 500))
311
+
312
+ rescued = false
313
+ begin
314
+ Nurego::Customer.new("test_customer").refresh
315
+ fail
316
+ rescue Nurego::APIError => e # we don't use assert_raises because we want to examine e
317
+ rescued = true
318
+ e.kind_of?(Nurego::APIError).should be_true
319
+ end
320
+
321
+ rescued.should be_true
322
+ end
323
+
324
+ end
325
+ end
326
+ end