nurego 1.0.1

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