# -*- coding: utf-8 -*- require File.expand_path('../../../test_helper', __FILE__) module Maestrano module API class ResourceTest < Test::Unit::TestCase include APITestHelper should "creating a new Resource should not fetch over the network" do @api_mock.expects(:get).never Maestrano::Account::Bill.new("someid") end should "creating a new Resource from a hash should not fetch over the network" do @api_mock.expects(:get).never Maestrano::Account::Bill.construct_from({ id: "somebill", object: "account_bill", price_cents: 2300, currency: 'AUD' }) end should "setting an attribute should not cause a network request" do @api_mock.expects(:get).never @api_mock.expects(:post).never c = Maestrano::Account::Bill.new("test_account_bill"); c.price_cents= 50000 end should "accessing id should not issue a fetch" do @api_mock.expects(:get).never c = Maestrano::Account::Bill.new("test_account_bill"); c.id end should "specifying invalid api credentials should raise an exception" do response = test_response(test_invalid_api_token_error, 401) assert_raises Maestrano::API::Error::AuthenticationError do @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401)) Maestrano::Account::Bill.retrieve("failing_bill") end end should "AuthenticationErrors should have an http status, http body, and JSON body" do response = test_response(test_invalid_api_token_error, 401) begin @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 401)) Maestrano::Account::Bill.retrieve("failing_bill") rescue Maestrano::API::Error::AuthenticationError => e assert_equal(401, e.http_status) assert_equal(true, !!e.http_body) assert_equal(true, !!e.json_body[:errors]) assert_equal(test_invalid_api_token_error['errors'].first.join(" "), e.json_body[:errors].first.join(" ")) end end context "when specifying per-object credentials" do context "with no global API key set" do setup do @original_api_key = Maestrano.param('api.key') Maestrano.configure { |c| c.api_key = nil } end teardown do Maestrano.configure { |c| c.api_key = @original_api_key } end should "use the per-object credential when creating" do Maestrano::API::Operation::Base.expects(:execute_request).with do |opts| opts[:headers][:authorization] == "Basic #{Base64.strict_encode64('someid:somekey')}" end.returns(test_response(test_account_bill)) Maestrano::Account::Bill.create({ group_id: 'cld-1', price_cents: 23000, currency: 'AUD', description: 'Some bill' }, 'someid:somekey' ) end end context "with a global API key set" do should "use the per-object credential when creating" do Maestrano::API::Operation::Base.expects(:execute_request).with do |opts| opts[:headers][:authorization] == "Basic #{Base64.strict_encode64('someid:somekey')}" end.returns(test_response(test_account_bill)) Maestrano::Account::Bill.create({ group_id: 'cld-1', price_cents: 23000, currency: 'AUD', description: 'Some bill' }, 'someid:somekey' ) end end end context "with valid credentials" do should "urlencode values in GET params" do response = test_response(test_account_bill_array) @api_mock.expects(:get).with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?bill=test%20bill", nil, nil).returns(response) bills = Maestrano::Account::Bill.all(:bill => 'test bill').data assert bills.kind_of? Array end should "construct URL properly with base query parameters" do response = test_response(test_account_bill_array) @api_mock.expects(:get).with("#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?bill=test_account_bill", nil, nil).returns(response) bills = Maestrano::Account::Bill.all(:bill => 'test_account_bill') end should "construct URL properly with multiple query parameters" do response = test_response(test_account_bill_array) @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) bills = Maestrano::Account::Bill.all(bill:'test_account_bill', paid: true) end should "a 400 should give an InvalidRequestError with http status, body, and JSON body" do response = test_response(test_missing_id_error, 400) @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404)) begin Maestrano::Account::Bill.retrieve("foo") rescue Maestrano::API::Error::InvalidRequestError => e assert_equal(400, e.http_status) assert_equal(true, !!e.http_body) assert_equal(true, e.json_body.kind_of?(Hash)) end end should "a 401 should give an AuthenticationError with http status, body, and JSON body" do response = test_response(test_missing_id_error, 401) @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404)) begin Maestrano::Account::Bill.retrieve("foo") rescue Maestrano::API::Error::AuthenticationError => e assert_equal(401, e.http_status) assert_equal(true, !!e.http_body) assert_equal(true, e.json_body.kind_of?(Hash)) end end should "a 404 should give an InvalidRequestError with http status, body, and JSON body" do response = test_response(test_missing_id_error, 404) @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404)) begin Maestrano::Account::Bill.retrieve("foo") rescue Maestrano::API::Error::InvalidRequestError => e assert_equal(404, e.http_status) assert_equal(true, !!e.http_body) assert_equal(true, e.json_body.kind_of?(Hash)) end end should "setting a nil value for a param should exclude that param from the GET request" do @api_mock.expects(:get).with do |url, api_token, params| uri = URI(url) query = CGI.parse(uri.query) (url =~ %r{^#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills?} && query.keys.sort == ['offset', 'sad']) end.returns(test_response(test_account_bill_array_one)) Maestrano::Account::Bill.all(:count => nil, :offset => 5, :sad => false) end should "setting a nil value for a param should exclude that param from the POST request" do @api_mock.expects(:post).with do |url, api_token, params| url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills" && api_token.nil? && CGI.parse(params) == { 'group_id' => ['cld-1'], 'price_cents' => ['23000'], 'currency' => ['AUD'] } end.returns(test_response(test_account_bill)) Maestrano::Account::Bill.create({ group_id: 'cld-1', price_cents: 23000, currency: 'AUD', description: nil }) end should "requesting with a unicode ID should result in a request" do response = test_response(test_missing_id_error, 404) @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)) c = Maestrano::Account::Bill.new("☃") assert_raises(Maestrano::API::Error::InvalidRequestError) { c.refresh } end should "requesting with no ID should result in an InvalidRequestError with no request" do c = Maestrano::Account::Bill.new assert_raises(Maestrano::API::Error::InvalidRequestError) { c.refresh } end should "making a GET request with parameters should have a query string and no body" do params = { :limit => 1 } @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)) Maestrano::Account::Bill.all(params) end should "making a POST request with parameters should have a body and no query string" do date = Time.now.utc params = { group_id: 'cld-1', price_cents: 23000, currency: 'AUD', description: 'Some bill', period_started_at: date } @api_mock.expects(:post).once.with do |url, get, post| get.nil? && CGI.parse(post) == {"group_id"=>["cld-1"], "price_cents"=>["23000"], "currency"=>["AUD"], "description"=>["Some bill"], "period_started_at" => ["#{date.iso8601}"]} end.returns(test_response(test_account_bill)) Maestrano::Account::Bill.create(params) end should "loading an object should issue a GET request" do @api_mock.expects(:get).once.returns(test_response(test_account_bill)) c = Maestrano::Account::Bill.new("test_account_bill") c.refresh end should "using array accessors should be the same as the method interface" do @api_mock.expects(:get).once.returns(test_response(test_account_bill)) c = Maestrano::Account::Bill.new("test_account_bill") c.refresh assert_equal c.created_at, c[:created_at] assert_equal c.created_at, c['created_at'] date = Time.now.utc.iso8601 c['created'] = date assert_equal c.created, date end # Not related object defined on any model yet # should "accessing a property other than id or parent on an unfetched object should fetch it" do # @api_mock.expects(:get).once.returns(test_response(test_account_bill)) # c = Maestrano::Account::Bill.new("test_account_bill") # c.price_cents # end should "updating an object should issue a PUT request with only the changed properties" do @api_mock.expects(:put).with do |url, api_token, params| url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills/bill-1" && api_token.nil? && CGI.parse(params) == {'description' => ['another_mn']} end.once.returns(test_response(test_account_bill)) c = Maestrano::Account::Bill.construct_from(test_account_bill[:data]) class << c include Maestrano::API::Operation::Update end c.description = "another_mn" c.save end should "updating should merge in returned properties" do @api_mock.expects(:put).once.returns(test_response(test_account_bill)) c = Maestrano::Account::Bill.new(test_account_bill[:data]) class << c include Maestrano::API::Operation::Update end c.description = "another_mn" c.save end should "deleting should send no props and result in an object that has no props other deleted" do @api_mock.expects(:get).never @api_mock.expects(:post).never @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)) c = Maestrano::Account::Bill.construct_from(test_account_bill[:data]) class << c include Maestrano::API::Operation::Delete end c.delete assert_raises NoMethodError do c.livemode end end # should "loading an object with properties that have specific types should instantiate those classes" do # @api_mock.expects(:get).once.returns(test_response(test_account_bill)) # c = Maestrano::Account::Bill.retrieve("test_account_bill") # assert c.card.kind_of?(Maestrano::API::Object) && c.card.object == 'card' # end should "loading all of a Resource should return an array of recursively instantiated objects" do @api_mock.expects(:get).once.returns(test_response(test_account_bill_array)) c = Maestrano::Account::Bill.all.data assert c.kind_of? Array assert c[0].kind_of? Maestrano::Account::Bill # No object to test for the moment #assert c[0].card.kind_of?(Maestrano::API::Object) && c[0].card.object == 'card' end context "error checking" do should "404s should raise an InvalidRequestError" do response = test_response(test_missing_id_error, 404) @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 404)) rescued = false begin Maestrano::Account::Bill.new("test_account_bill").refresh assert false #shouldn't get here either rescue Maestrano::API::Error::InvalidRequestError => e # we don't use assert_raises because we want to examine e rescued = true assert e.kind_of? Maestrano::API::Error::InvalidRequestError assert_equal "id", e.param assert_equal 'id does not exist', e.message end assert_equal true, rescued end should "5XXs should raise an API::Error" do response = test_response(test_api_error, 500) @api_mock.expects(:get).once.raises(RestClient::ExceptionWithResponse.new(response, 500)) rescued = false begin Maestrano::Account::Bill.new("test_account_bill").refresh assert false #shouldn't get here either rescue Maestrano::API::Error::BaseError => e # we don't use assert_raises because we want to examine e rescued = true assert e.kind_of? Maestrano::API::Error::BaseError end assert_equal true, rescued end end end end end end