maestrano 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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +43 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/Rakefile +32 -0
- data/bin/maestrano-console +9 -0
- data/lib/maestrano.rb +114 -0
- data/lib/maestrano/account/bill.rb +14 -0
- data/lib/maestrano/api/error/authentication_error.rb +8 -0
- data/lib/maestrano/api/error/base_error.rb +24 -0
- data/lib/maestrano/api/error/connection_error.rb +8 -0
- data/lib/maestrano/api/error/invalid_request_error.rb +14 -0
- data/lib/maestrano/api/list_object.rb +37 -0
- data/lib/maestrano/api/object.rb +187 -0
- data/lib/maestrano/api/operation/base.rb +216 -0
- data/lib/maestrano/api/operation/create.rb +18 -0
- data/lib/maestrano/api/operation/delete.rb +13 -0
- data/lib/maestrano/api/operation/list.rb +18 -0
- data/lib/maestrano/api/operation/update.rb +59 -0
- data/lib/maestrano/api/resource.rb +39 -0
- data/lib/maestrano/api/util.rb +121 -0
- data/lib/maestrano/saml/attribute_value.rb +15 -0
- data/lib/maestrano/saml/metadata.rb +64 -0
- data/lib/maestrano/saml/request.rb +93 -0
- data/lib/maestrano/saml/response.rb +201 -0
- data/lib/maestrano/saml/schemas/saml20assertion_schema.xsd +283 -0
- data/lib/maestrano/saml/schemas/saml20protocol_schema.xsd +302 -0
- data/lib/maestrano/saml/schemas/xenc_schema.xsd +146 -0
- data/lib/maestrano/saml/schemas/xmldsig_schema.xsd +318 -0
- data/lib/maestrano/saml/settings.rb +37 -0
- data/lib/maestrano/saml/validation_error.rb +7 -0
- data/lib/maestrano/sso.rb +81 -0
- data/lib/maestrano/sso/base_group.rb +31 -0
- data/lib/maestrano/sso/base_user.rb +75 -0
- data/lib/maestrano/sso/group.rb +24 -0
- data/lib/maestrano/sso/session.rb +63 -0
- data/lib/maestrano/sso/user.rb +34 -0
- data/lib/maestrano/version.rb +3 -0
- data/lib/maestrano/xml_security/signed_document.rb +170 -0
- data/maestrano.gemspec +32 -0
- data/test/helpers/api_helpers.rb +82 -0
- data/test/helpers/saml_helpers.rb +62 -0
- data/test/maestrano/account/bill_test.rb +48 -0
- data/test/maestrano/api/list_object_test.rb +20 -0
- data/test/maestrano/api/object_test.rb +28 -0
- data/test/maestrano/api/resource_test.rb +343 -0
- data/test/maestrano/api/util_test.rb +31 -0
- data/test/maestrano/maestrano_test.rb +49 -0
- data/test/maestrano/saml/request_test.rb +168 -0
- data/test/maestrano/saml/response_test.rb +290 -0
- data/test/maestrano/saml/settings_test.rb +51 -0
- data/test/maestrano/sso/base_group_test.rb +54 -0
- data/test/maestrano/sso/base_user_test.rb +114 -0
- data/test/maestrano/sso/group_test.rb +47 -0
- data/test/maestrano/sso/session_test.rb +108 -0
- data/test/maestrano/sso/user_test.rb +65 -0
- data/test/maestrano/sso_test.rb +81 -0
- data/test/maestrano/xml_security/signed_document.rb +163 -0
- data/test/support/saml/certificates/certificate1 +12 -0
- data/test/support/saml/certificates/r1_certificate2_base64 +1 -0
- data/test/support/saml/responses/adfs_response_sha1.xml +46 -0
- data/test/support/saml/responses/adfs_response_sha256.xml +46 -0
- data/test/support/saml/responses/adfs_response_sha384.xml +46 -0
- data/test/support/saml/responses/adfs_response_sha512.xml +46 -0
- data/test/support/saml/responses/no_signature_ns.xml +48 -0
- data/test/support/saml/responses/open_saml_response.xml +56 -0
- data/test/support/saml/responses/r1_response6.xml.base64 +1 -0
- data/test/support/saml/responses/response1.xml.base64 +1 -0
- data/test/support/saml/responses/response2.xml.base64 +79 -0
- data/test/support/saml/responses/response3.xml.base64 +66 -0
- data/test/support/saml/responses/response4.xml.base64 +93 -0
- data/test/support/saml/responses/response5.xml.base64 +102 -0
- data/test/support/saml/responses/response_with_ampersands.xml +139 -0
- data/test/support/saml/responses/response_with_ampersands.xml.base64 +93 -0
- data/test/support/saml/responses/response_with_multiple_attribute_values.xml +57 -0
- data/test/support/saml/responses/simple_saml_php.xml +71 -0
- data/test/support/saml/responses/starfield_response.xml.base64 +1 -0
- data/test/support/saml/responses/wrapped_response_2.xml.base64 +150 -0
- data/test/test_helper.rb +46 -0
- metadata +305 -0
data/maestrano.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'maestrano/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'maestrano'
|
7
|
+
s.version = Maestrano::VERSION
|
8
|
+
s.summary = 'Ruby bindings for the Maestrano API'
|
9
|
+
s.description = 'Maestrano is the next generation marketplace for SME applications. See https://maestrano.com for details.'
|
10
|
+
s.authors = ['Arnaud Lachaume']
|
11
|
+
s.email = ['arnaud.lachaume@maestrano.com']
|
12
|
+
s.homepage = 'https://maestrano.com'
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
|
20
|
+
s.add_dependency('rest-client', '~> 1.4')
|
21
|
+
s.add_dependency('mime-types', '~> 1.25')
|
22
|
+
s.add_dependency('json', '~> 1.8')
|
23
|
+
|
24
|
+
s.add_development_dependency('test-unit', '~> 2')
|
25
|
+
s.add_development_dependency('mocha', '~> 0.13')
|
26
|
+
s.add_development_dependency('shoulda', '~> 2.11')
|
27
|
+
s.add_development_dependency('timecop', '<= 0.6.0')
|
28
|
+
s.add_development_dependency('rake', '~> 10')
|
29
|
+
|
30
|
+
s.add_runtime_dependency("uuid", ["~> 2.3"])
|
31
|
+
s.add_runtime_dependency("nokogiri", [">= 1.5.0"])
|
32
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module APITestHelper
|
2
|
+
def test_response(body={}, code=200)
|
3
|
+
# When an exception is raised, restclient clobbers method_missing. Hence we
|
4
|
+
# can't just use the stubs interface.
|
5
|
+
body = JSON.generate(body) if !(body.kind_of? String)
|
6
|
+
m = mock
|
7
|
+
m.instance_variable_set('@resp_values', { :body => body, :code => code })
|
8
|
+
def m.body; @resp_values[:body]; end
|
9
|
+
def m.code; @resp_values[:code]; end
|
10
|
+
m
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_account_bill_content(params={})
|
14
|
+
{
|
15
|
+
object: 'account_bill',
|
16
|
+
id: 'bill-1',
|
17
|
+
group_id: 'cld-1',
|
18
|
+
created_at: Time.now.utc.iso8601,
|
19
|
+
price_cents: 2300,
|
20
|
+
status: 'submitted',
|
21
|
+
currency: 'AUD',
|
22
|
+
units: 1,
|
23
|
+
description: 'Bill for something',
|
24
|
+
period_start: Time.now.utc.iso8601,
|
25
|
+
period_end: (Time.now + 3600000).utc.iso8601,
|
26
|
+
}.merge(params)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_account_bill(params={})
|
30
|
+
{
|
31
|
+
success: true,
|
32
|
+
errors: {},
|
33
|
+
data: test_account_bill_content(params)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_account_bill_array
|
38
|
+
{
|
39
|
+
success: true,
|
40
|
+
errors: {},
|
41
|
+
data: [test_account_bill_content, test_account_bill_content, test_account_bill_content],
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_account_bill_array_one
|
46
|
+
{
|
47
|
+
success: true,
|
48
|
+
errors: {},
|
49
|
+
data: [test_account_bill_content],
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_invalid_api_key_error
|
54
|
+
{
|
55
|
+
'success' => false,
|
56
|
+
'data' => {},
|
57
|
+
"errors" => {
|
58
|
+
"authentication" => ["Invalid API token"],
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_missing_id_error
|
64
|
+
{
|
65
|
+
'success' => false,
|
66
|
+
'data' => {},
|
67
|
+
'errors' => {
|
68
|
+
'id' => ["does not exist"]
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_api_error
|
74
|
+
{
|
75
|
+
'success' => false,
|
76
|
+
'data' => {},
|
77
|
+
'errors' => {
|
78
|
+
'system' => ["A system error occured. Please retry later or contact support@maestrano.com if the issue persists."]
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module SamlTestHelper
|
2
|
+
def fixture(document, base64 = true)
|
3
|
+
response = Dir.glob(File.join(File.dirname(__FILE__), '..', 'support', 'saml', "responses", "#{document}*")).first
|
4
|
+
if base64 && response =~ /\.xml$/
|
5
|
+
Base64.encode64(File.read(response))
|
6
|
+
else
|
7
|
+
File.read(response)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def response_document
|
12
|
+
@response_document ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'response1.xml.base64'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_document_2
|
16
|
+
@response_document2 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'response2.xml.base64'))
|
17
|
+
end
|
18
|
+
|
19
|
+
def response_document_3
|
20
|
+
@response_document3 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'response3.xml.base64'))
|
21
|
+
end
|
22
|
+
|
23
|
+
def response_document_4
|
24
|
+
@response_document4 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'response4.xml.base64'))
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_document_5
|
28
|
+
@response_document5 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'response5.xml.base64'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def r1_response_document_6
|
32
|
+
@response_document6 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'r1_response6.xml.base64'))
|
33
|
+
end
|
34
|
+
|
35
|
+
def ampersands_response
|
36
|
+
@ampersands_resposne ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'response_with_ampersands.xml.base64'))
|
37
|
+
end
|
38
|
+
|
39
|
+
def response_document_6
|
40
|
+
doc = Base64.decode64(response_document)
|
41
|
+
doc.gsub!(/NotBefore=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotBefore=\"#{(Time.now-300).getutc.strftime("%Y-%m-%dT%XZ")}\"")
|
42
|
+
doc.gsub!(/NotOnOrAfter=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotOnOrAfter=\"#{(Time.now+300).getutc.strftime("%Y-%m-%dT%XZ")}\"")
|
43
|
+
Base64.encode64(doc)
|
44
|
+
end
|
45
|
+
|
46
|
+
def wrapped_response_2
|
47
|
+
@wrapped_response_2 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'responses', 'wrapped_response_2.xml.base64'))
|
48
|
+
end
|
49
|
+
|
50
|
+
def signature_fingerprint_1
|
51
|
+
@signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
|
52
|
+
end
|
53
|
+
|
54
|
+
def signature_1
|
55
|
+
@signature1 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'certificates', 'certificate1'))
|
56
|
+
end
|
57
|
+
|
58
|
+
def r1_signature_2
|
59
|
+
@signature2 ||= File.read(File.join(File.dirname(__FILE__), '..', 'support', 'saml', 'certificates', 'r1_certificate2_base64'))
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module Maestrano
|
4
|
+
module Account
|
5
|
+
class BillTest < Test::Unit::TestCase
|
6
|
+
include APITestHelper
|
7
|
+
|
8
|
+
should "bills should be listable" do
|
9
|
+
@api_mock.expects(:get).once.returns(test_response(test_account_bill_array))
|
10
|
+
c = Maestrano::Account::Bill.all
|
11
|
+
assert c.data.kind_of? Array
|
12
|
+
c.each do |bill|
|
13
|
+
assert bill.kind_of?(Maestrano::Account::Bill)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
should "bills should be cancellable" do
|
18
|
+
@api_mock.expects(:delete).once.returns(test_response(test_account_bill))
|
19
|
+
c = Maestrano::Account::Bill.construct_from(test_account_bill[:data])
|
20
|
+
c.cancel
|
21
|
+
end
|
22
|
+
|
23
|
+
should "bills should not be updateable" do
|
24
|
+
assert_raises NoMethodError do
|
25
|
+
@api_mock.stubs(:put).returns(test_response(test_account_bill))
|
26
|
+
c = Maestrano::Account::Bill.construct_from(test_account_bill[:data])
|
27
|
+
c.save
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
should "create should successfully create a remote bill when passed correct parameters" do
|
33
|
+
@api_mock.expects(:post).with do |url, api_key, params|
|
34
|
+
url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills" && api_key.nil? &&
|
35
|
+
CGI.parse(params) == {"group_id"=>["cld-1"], "price_cents"=>["23000"], "currency"=>["AUD"], "description"=>["Some bill"]}
|
36
|
+
end.once.returns(test_response(test_account_bill))
|
37
|
+
|
38
|
+
bill = Maestrano::Account::Bill.create({
|
39
|
+
group_id: 'cld-1',
|
40
|
+
price_cents: 23000,
|
41
|
+
currency: 'AUD',
|
42
|
+
description: 'Some bill'
|
43
|
+
})
|
44
|
+
assert bill.id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module Maestrano
|
4
|
+
module API
|
5
|
+
class ListObjectTest < Test::Unit::TestCase
|
6
|
+
include APITestHelper
|
7
|
+
|
8
|
+
should "be able to retrieve full lists given a listobject" do
|
9
|
+
@api_mock.expects(:get).twice.returns(test_response(test_account_bill_array))
|
10
|
+
c = Maestrano::Account::Bill.all
|
11
|
+
assert c.kind_of?(Maestrano::API::ListObject)
|
12
|
+
assert_equal('account/bills', c.url)
|
13
|
+
all = c.all
|
14
|
+
assert all.kind_of?(Maestrano::API::ListObject)
|
15
|
+
assert_equal('account/bills', all.url)
|
16
|
+
assert all.data.kind_of?(Array)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module Maestrano
|
4
|
+
module API
|
5
|
+
class ObjectTest < Test::Unit::TestCase
|
6
|
+
should "implement #respond_to correctly" do
|
7
|
+
obj = Maestrano::API::Object.construct_from({ :id => 1, :foo => 'bar' })
|
8
|
+
assert obj.respond_to?(:id)
|
9
|
+
assert obj.respond_to?(:foo)
|
10
|
+
assert !obj.respond_to?(:baz)
|
11
|
+
end
|
12
|
+
|
13
|
+
should "marshal a maestrano object correctly" do
|
14
|
+
date = Time.now.utc
|
15
|
+
obj = Maestrano::API::Object.construct_from({
|
16
|
+
id: 1,
|
17
|
+
name: 'Maestrano',
|
18
|
+
some_date: date.iso8601
|
19
|
+
}, 'apikey')
|
20
|
+
m = Marshal.load(Marshal.dump(obj))
|
21
|
+
assert_equal 1, m.id
|
22
|
+
assert_equal 'Maestrano', m.name
|
23
|
+
assert_equal 'apikey', m.api_key
|
24
|
+
assert_equal date.iso8601, m.some_date.iso8601
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
3
|
+
|
4
|
+
module Maestrano
|
5
|
+
module API
|
6
|
+
class ResourceTest < Test::Unit::TestCase
|
7
|
+
include APITestHelper
|
8
|
+
|
9
|
+
should "creating a new Resource should not fetch over the network" do
|
10
|
+
@api_mock.expects(:get).never
|
11
|
+
Maestrano::Account::Bill.new("someid")
|
12
|
+
end
|
13
|
+
|
14
|
+
should "creating a new Resource from a hash should not fetch over the network" do
|
15
|
+
@api_mock.expects(:get).never
|
16
|
+
Maestrano::Account::Bill.construct_from({
|
17
|
+
id: "somebill",
|
18
|
+
object: "account_bill",
|
19
|
+
price_cents: 2300,
|
20
|
+
currency: 'AUD'
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
should "setting an attribute should not cause a network request" do
|
25
|
+
@api_mock.expects(:get).never
|
26
|
+
@api_mock.expects(:post).never
|
27
|
+
c = Maestrano::Account::Bill.new("test_account_bill");
|
28
|
+
c.price_cents= 50000
|
29
|
+
end
|
30
|
+
|
31
|
+
should "accessing id should not issue a fetch" do
|
32
|
+
@api_mock.expects(:get).never
|
33
|
+
c = Maestrano::Account::Bill.new("test_account_bill");
|
34
|
+
c.id
|
35
|
+
end
|
36
|
+
|
37
|
+
should "specifying invalid api credentials should raise an exception" do
|
38
|
+
response = test_response(test_invalid_api_key_error, 401)
|
39
|
+
assert_raises Maestrano::API::Error::AuthenticationError do
|
40
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401))
|
41
|
+
Maestrano::Account::Bill.retrieve("failing_bill")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
should "AuthenticationErrors should have an http status, http body, and JSON body" do
|
46
|
+
response = test_response(test_invalid_api_key_error, 401)
|
47
|
+
begin
|
48
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401))
|
49
|
+
Maestrano::Account::Bill.retrieve("failing_bill")
|
50
|
+
rescue Maestrano::API::Error::AuthenticationError => e
|
51
|
+
assert_equal(401, e.http_status)
|
52
|
+
assert_equal(true, !!e.http_body)
|
53
|
+
assert_equal(true, !!e.json_body[:errors])
|
54
|
+
assert_equal(test_invalid_api_key_error['errors'].first.join(" "), e.json_body[:errors].first.join(" "))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "when specifying per-object credentials" do
|
59
|
+
context "with no global API key set" do
|
60
|
+
setup do
|
61
|
+
@original_api_key = Maestrano.param('api_key')
|
62
|
+
Maestrano.configure { |c| c.api_key = nil }
|
63
|
+
end
|
64
|
+
|
65
|
+
teardown do
|
66
|
+
Maestrano.configure { |c| c.api_key = @original_api_key }
|
67
|
+
end
|
68
|
+
|
69
|
+
should "use the per-object credential when creating" do
|
70
|
+
Maestrano::API::Operation::Base.expects(:execute_request).with do |opts|
|
71
|
+
opts[:headers][:authorization] == "Basic #{Base64.encode64('sk_test_local:')}"
|
72
|
+
end.returns(test_response(test_account_bill))
|
73
|
+
|
74
|
+
Maestrano::Account::Bill.create({
|
75
|
+
group_id: 'cld-1',
|
76
|
+
price_cents: 23000,
|
77
|
+
currency: 'AUD',
|
78
|
+
description: 'Some bill'
|
79
|
+
},
|
80
|
+
'sk_test_local'
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with a global API key set" do
|
86
|
+
should "use the per-object credential when creating" do
|
87
|
+
Maestrano::API::Operation::Base.expects(:execute_request).with do |opts|
|
88
|
+
opts[:headers][:authorization] == "Basic #{Base64.encode64('local:')}"
|
89
|
+
end.returns(test_response(test_account_bill))
|
90
|
+
|
91
|
+
Maestrano::Account::Bill.create({
|
92
|
+
group_id: 'cld-1',
|
93
|
+
price_cents: 23000,
|
94
|
+
currency: 'AUD',
|
95
|
+
description: 'Some bill'
|
96
|
+
},
|
97
|
+
'local'
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "with valid credentials" do
|
104
|
+
should "urlencode values in GET params" do
|
105
|
+
response = test_response(test_account_bill_array)
|
106
|
+
@api_mock.expects(:get).with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?bill=test%20bill", nil, nil).returns(response)
|
107
|
+
bills = Maestrano::Account::Bill.all(:bill => 'test bill').data
|
108
|
+
assert bills.kind_of? Array
|
109
|
+
end
|
110
|
+
|
111
|
+
should "construct URL properly with base query parameters" do
|
112
|
+
response = test_response(test_account_bill_array)
|
113
|
+
@api_mock.expects(:get).with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?bill=test_account_bill", nil, nil).returns(response)
|
114
|
+
bills = Maestrano::Account::Bill.all(:bill => 'test_account_bill')
|
115
|
+
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
should "construct URL properly with multiple query parameters" do
|
120
|
+
response = test_response(test_account_bill_array)
|
121
|
+
@api_mock.expects(:get).with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?bill=test_account_bill&paid=true", nil, nil).returns(response)
|
122
|
+
bills = Maestrano::Account::Bill.all(bill:'test_account_bill', paid: true)
|
123
|
+
end
|
124
|
+
|
125
|
+
should "a 400 should give an InvalidRequestError with http status, body, and JSON body" do
|
126
|
+
response = test_response(test_missing_id_error, 400)
|
127
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
|
128
|
+
begin
|
129
|
+
Maestrano::Account::Bill.retrieve("foo")
|
130
|
+
rescue Maestrano::API::Error::InvalidRequestError => e
|
131
|
+
assert_equal(400, e.http_status)
|
132
|
+
assert_equal(true, !!e.http_body)
|
133
|
+
assert_equal(true, e.json_body.kind_of?(Hash))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
should "a 401 should give an AuthenticationError with http status, body, and JSON body" do
|
138
|
+
response = test_response(test_missing_id_error, 401)
|
139
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
|
140
|
+
begin
|
141
|
+
Maestrano::Account::Bill.retrieve("foo")
|
142
|
+
rescue Maestrano::API::Error::AuthenticationError => e
|
143
|
+
assert_equal(401, e.http_status)
|
144
|
+
assert_equal(true, !!e.http_body)
|
145
|
+
assert_equal(true, e.json_body.kind_of?(Hash))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
should "a 404 should give an InvalidRequestError with http status, body, and JSON body" do
|
150
|
+
response = test_response(test_missing_id_error, 404)
|
151
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
|
152
|
+
begin
|
153
|
+
Maestrano::Account::Bill.retrieve("foo")
|
154
|
+
rescue Maestrano::API::Error::InvalidRequestError => e
|
155
|
+
assert_equal(404, e.http_status)
|
156
|
+
assert_equal(true, !!e.http_body)
|
157
|
+
assert_equal(true, e.json_body.kind_of?(Hash))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
should "setting a nil value for a param should exclude that param from the GET request" do
|
162
|
+
@api_mock.expects(:get).with do |url, api_key, params|
|
163
|
+
uri = URI(url)
|
164
|
+
query = CGI.parse(uri.query)
|
165
|
+
(url =~ %r{^#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?} &&
|
166
|
+
query.keys.sort == ['offset', 'sad'])
|
167
|
+
end.returns(test_response(test_account_bill_array_one))
|
168
|
+
Maestrano::Account::Bill.all(:count => nil, :offset => 5, :sad => false)
|
169
|
+
end
|
170
|
+
|
171
|
+
should "setting a nil value for a param should exclude that param from the POST request" do
|
172
|
+
@api_mock.expects(:post).with do |url, api_key, params|
|
173
|
+
url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills" &&
|
174
|
+
api_key.nil? &&
|
175
|
+
CGI.parse(params) == { 'group_id' => ['cld-1'], 'price_cents' => ['23000'], 'currency' => ['AUD'] }
|
176
|
+
end.returns(test_response(test_account_bill))
|
177
|
+
Maestrano::Account::Bill.create({
|
178
|
+
group_id: 'cld-1',
|
179
|
+
price_cents: 23000,
|
180
|
+
currency: 'AUD',
|
181
|
+
description: nil
|
182
|
+
})
|
183
|
+
end
|
184
|
+
|
185
|
+
should "requesting with a unicode ID should result in a request" do
|
186
|
+
response = test_response(test_missing_id_error, 404)
|
187
|
+
@api_mock.expects(:get).once.with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills/%E2%98%83", nil, nil).raises(RestClient::ExceptionWithResponse.new(response, 404))
|
188
|
+
c = Maestrano::Account::Bill.new("☃")
|
189
|
+
assert_raises(Maestrano::API::Error::InvalidRequestError) { c.refresh }
|
190
|
+
end
|
191
|
+
|
192
|
+
should "requesting with no ID should result in an InvalidRequestError with no request" do
|
193
|
+
c = Maestrano::Account::Bill.new
|
194
|
+
assert_raises(Maestrano::API::Error::InvalidRequestError) { c.refresh }
|
195
|
+
end
|
196
|
+
|
197
|
+
should "making a GET request with parameters should have a query string and no body" do
|
198
|
+
params = { :limit => 1 }
|
199
|
+
@api_mock.expects(:get).once.with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?limit=1", nil, nil).returns(test_response(test_account_bill_array_one))
|
200
|
+
Maestrano::Account::Bill.all(params)
|
201
|
+
end
|
202
|
+
|
203
|
+
should "making a POST request with parameters should have a body and no query string" do
|
204
|
+
date = Time.now.utc
|
205
|
+
params = {
|
206
|
+
group_id: 'cld-1',
|
207
|
+
price_cents: 23000,
|
208
|
+
currency: 'AUD',
|
209
|
+
description: 'Some bill',
|
210
|
+
period_started_at: date
|
211
|
+
}
|
212
|
+
@api_mock.expects(:post).once.with do |url, get, post|
|
213
|
+
get.nil? &&
|
214
|
+
CGI.parse(post) == {"group_id"=>["cld-1"], "price_cents"=>["23000"], "currency"=>["AUD"], "description"=>["Some bill"], "period_started_at" => ["#{date.iso8601}"]}
|
215
|
+
end.returns(test_response(test_account_bill))
|
216
|
+
Maestrano::Account::Bill.create(params)
|
217
|
+
end
|
218
|
+
|
219
|
+
should "loading an object should issue a GET request" do
|
220
|
+
@api_mock.expects(:get).once.returns(test_response(test_account_bill))
|
221
|
+
c = Maestrano::Account::Bill.new("test_account_bill")
|
222
|
+
c.refresh
|
223
|
+
end
|
224
|
+
|
225
|
+
should "using array accessors should be the same as the method interface" do
|
226
|
+
@api_mock.expects(:get).once.returns(test_response(test_account_bill))
|
227
|
+
c = Maestrano::Account::Bill.new("test_account_bill")
|
228
|
+
c.refresh
|
229
|
+
assert_equal c.created_at, c[:created_at]
|
230
|
+
assert_equal c.created_at, c['created_at']
|
231
|
+
date = Time.now.utc.iso8601
|
232
|
+
c['created'] = date
|
233
|
+
assert_equal c.created, date
|
234
|
+
end
|
235
|
+
|
236
|
+
# Not related object defined on any model yet
|
237
|
+
# should "accessing a property other than id or parent on an unfetched object should fetch it" do
|
238
|
+
# @api_mock.expects(:get).once.returns(test_response(test_account_bill))
|
239
|
+
# c = Maestrano::Account::Bill.new("test_account_bill")
|
240
|
+
# c.price_cents
|
241
|
+
# end
|
242
|
+
|
243
|
+
should "updating an object should issue a PUT request with only the changed properties" do
|
244
|
+
@api_mock.expects(:put).with do |url, api_key, params|
|
245
|
+
url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills/bill-1" &&
|
246
|
+
api_key.nil? && CGI.parse(params) == {'description' => ['another_mn']}
|
247
|
+
end.once.returns(test_response(test_account_bill))
|
248
|
+
|
249
|
+
c = Maestrano::Account::Bill.construct_from(test_account_bill[:data])
|
250
|
+
class << c
|
251
|
+
include Maestrano::API::Operation::Update
|
252
|
+
end
|
253
|
+
|
254
|
+
c.description = "another_mn"
|
255
|
+
c.save
|
256
|
+
end
|
257
|
+
|
258
|
+
should "updating should merge in returned properties" do
|
259
|
+
@api_mock.expects(:put).once.returns(test_response(test_account_bill))
|
260
|
+
c = Maestrano::Account::Bill.new(test_account_bill[:data])
|
261
|
+
class << c
|
262
|
+
include Maestrano::API::Operation::Update
|
263
|
+
end
|
264
|
+
|
265
|
+
c.description = "another_mn"
|
266
|
+
c.save
|
267
|
+
end
|
268
|
+
|
269
|
+
should "deleting should send no props and result in an object that has no props other deleted" do
|
270
|
+
@api_mock.expects(:get).never
|
271
|
+
@api_mock.expects(:post).never
|
272
|
+
@api_mock.expects(:delete).with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills/bill-1", nil, nil).once.returns(test_response(test_account_bill))
|
273
|
+
|
274
|
+
c = Maestrano::Account::Bill.construct_from(test_account_bill[:data])
|
275
|
+
class << c
|
276
|
+
include Maestrano::API::Operation::Delete
|
277
|
+
end
|
278
|
+
|
279
|
+
c.delete
|
280
|
+
|
281
|
+
|
282
|
+
assert_raises NoMethodError do
|
283
|
+
c.livemode
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# should "loading an object with properties that have specific types should instantiate those classes" do
|
288
|
+
# @api_mock.expects(:get).once.returns(test_response(test_account_bill))
|
289
|
+
# c = Maestrano::Account::Bill.retrieve("test_account_bill")
|
290
|
+
# assert c.card.kind_of?(Maestrano::API::Object) && c.card.object == 'card'
|
291
|
+
# end
|
292
|
+
|
293
|
+
should "loading all of a Resource should return an array of recursively instantiated objects" do
|
294
|
+
@api_mock.expects(:get).once.returns(test_response(test_account_bill_array))
|
295
|
+
c = Maestrano::Account::Bill.all.data
|
296
|
+
assert c.kind_of? Array
|
297
|
+
assert c[0].kind_of? Maestrano::Account::Bill
|
298
|
+
|
299
|
+
# No object to test for the moment
|
300
|
+
#assert c[0].card.kind_of?(Maestrano::API::Object) && c[0].card.object == 'card'
|
301
|
+
end
|
302
|
+
|
303
|
+
context "error checking" do
|
304
|
+
|
305
|
+
should "404s should raise an InvalidRequestError" do
|
306
|
+
response = test_response(test_missing_id_error, 404)
|
307
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404))
|
308
|
+
|
309
|
+
rescued = false
|
310
|
+
begin
|
311
|
+
Maestrano::Account::Bill.new("test_account_bill").refresh
|
312
|
+
assert false #shouldn't get here either
|
313
|
+
rescue Maestrano::API::Error::InvalidRequestError => e # we don't use assert_raises because we want to examine e
|
314
|
+
rescued = true
|
315
|
+
assert e.kind_of? Maestrano::API::Error::InvalidRequestError
|
316
|
+
assert_equal "id", e.param
|
317
|
+
assert_equal 'id does not exist', e.message
|
318
|
+
end
|
319
|
+
|
320
|
+
assert_equal true, rescued
|
321
|
+
end
|
322
|
+
|
323
|
+
should "5XXs should raise an API::Error" do
|
324
|
+
response = test_response(test_api_error, 500)
|
325
|
+
@api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 500))
|
326
|
+
|
327
|
+
rescued = false
|
328
|
+
begin
|
329
|
+
Maestrano::Account::Bill.new("test_account_bill").refresh
|
330
|
+
assert false #shouldn't get here either
|
331
|
+
rescue Maestrano::API::Error::BaseError => e # we don't use assert_raises because we want to examine e
|
332
|
+
rescued = true
|
333
|
+
assert e.kind_of? Maestrano::API::Error::BaseError
|
334
|
+
end
|
335
|
+
|
336
|
+
assert_equal true, rescued
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|