sage_one 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +4 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +19 -0
  6. data/README.md +141 -0
  7. data/Rakefile +4 -0
  8. data/bin/autospec +16 -0
  9. data/bin/htmldiff +16 -0
  10. data/bin/ldiff +16 -0
  11. data/bin/rake +16 -0
  12. data/bin/rspec +16 -0
  13. data/bin/yard +16 -0
  14. data/bin/yardoc +16 -0
  15. data/bin/yri +16 -0
  16. data/lib/faraday/request/oauth2.rb +25 -0
  17. data/lib/faraday/response/convert_sdata_to_headers.rb +70 -0
  18. data/lib/faraday/response/raise_sage_one_exception.rb +42 -0
  19. data/lib/sage_one/client/contacts.rb +14 -0
  20. data/lib/sage_one/client/sales_invoices.rb +77 -0
  21. data/lib/sage_one/client.rb +33 -0
  22. data/lib/sage_one/configuration.rb +81 -0
  23. data/lib/sage_one/connection.rb +44 -0
  24. data/lib/sage_one/error.rb +39 -0
  25. data/lib/sage_one/oauth.rb +49 -0
  26. data/lib/sage_one/request.rb +72 -0
  27. data/lib/sage_one/version.rb +3 -0
  28. data/lib/sage_one.rb +31 -0
  29. data/sage_one.gemspec +32 -0
  30. data/spec/faraday/request/oauth2_spec.rb +44 -0
  31. data/spec/faraday/response/convert_sdata_to_headers_spec.rb +113 -0
  32. data/spec/faraday/response/raise_sage_one_exception_spec.rb +45 -0
  33. data/spec/fixtures/contact.json +28 -0
  34. data/spec/fixtures/invalid_sales_invoice.json +28 -0
  35. data/spec/fixtures/oauth/invalid_client.json +1 -0
  36. data/spec/fixtures/oauth/invalid_grant.json +1 -0
  37. data/spec/fixtures/oauth/oauth_token.json +4 -0
  38. data/spec/fixtures/sales_invoice.json +43 -0
  39. data/spec/fixtures/sales_invoices.json +90 -0
  40. data/spec/sage_one/client/contacts_spec.rb +19 -0
  41. data/spec/sage_one/client/sales_invoices_spec.rb +53 -0
  42. data/spec/sage_one/client_spec.rb +41 -0
  43. data/spec/sage_one/configuration_spec.rb +88 -0
  44. data/spec/sage_one/connection_spec.rb +36 -0
  45. data/spec/sage_one/oauth_spec.rb +44 -0
  46. data/spec/sage_one/request_spec.rb +113 -0
  47. data/spec/sage_one/version_spec.rb +7 -0
  48. data/spec/sage_one_spec.rb +38 -0
  49. data/spec/spec_helper.rb +76 -0
  50. metadata +301 -0
@@ -0,0 +1,81 @@
1
+ require 'faraday'
2
+ require 'sage_one/version'
3
+
4
+ module SageOne
5
+ # Provide numerous configuration options that control core behaviour.
6
+ module Configuration
7
+ VALID_OPTIONS_KEYS = [
8
+ :adapter,
9
+ :faraday_config_block,
10
+ :api_endpoint,
11
+ :proxy,
12
+ :access_token,
13
+ :client_id,
14
+ :client_secret,
15
+ :user_agent,
16
+ :request_host,
17
+ :auto_traversal,
18
+ :raw_response].freeze
19
+
20
+ DEFAULT_ADAPTER = Faraday.default_adapter
21
+ DEFAULT_API_ENDPOINT = 'https://app.sageone.com/api/v1/'.freeze
22
+ DEFAULT_USER_AGENT = "SageOne Ruby Gem #{SageOne::VERSION}".freeze
23
+
24
+ # Only get the first page when making paginated data requests
25
+ DEFAULT_AUTO_TRAVERSAL = false
26
+
27
+ # Parse Json, Mashify & convert SData when making requests
28
+ DEFAULT_RAW_RESPONSE = false
29
+
30
+ attr_accessor(*VALID_OPTIONS_KEYS)
31
+
32
+ # Override the default API endpoint, this ensures a trailing forward slash is added.
33
+ # @param [String] value for a different API endpoint
34
+ # @example
35
+ # SageOne.api_end_point = 'https://app.sageone.com/api/v2/'
36
+ def api_endpoint=(value)
37
+ @api_endpoint = File.join(value, "")
38
+ end
39
+
40
+ # Stores the given block which is called when the new Faraday::Connection
41
+ # is set up for all requests. This allow you to configure the connection,
42
+ # for example with your own middleware.
43
+ def faraday_config(&block)
44
+ @faraday_config_block = block
45
+ end
46
+
47
+ # Yields the given block passing in selfing allow you to set config options
48
+ # on the 'extend'-ing module.
49
+ # @example
50
+ # SageOne.configure do |config|
51
+ # config.access_token = 'my-access-token'
52
+ # end
53
+ def configure
54
+ yield self
55
+ end
56
+
57
+ # @return [Hash] All options with their current values
58
+ def options
59
+ VALID_OPTIONS_KEYS.inject({}){|o,k| o.merge!(k => send(k)) }
60
+ end
61
+
62
+ # When extended call reset
63
+ def self.extended(base)
64
+ base.reset
65
+ end
66
+
67
+ # Sets the options to the default values
68
+ def reset
69
+ self.adapter = DEFAULT_ADAPTER
70
+ self.api_endpoint = DEFAULT_API_ENDPOINT
71
+ self.proxy = nil
72
+ self.access_token = nil
73
+ self.client_id = nil
74
+ self.client_secret = nil
75
+ self.request_host = nil
76
+ self.user_agent = DEFAULT_USER_AGENT
77
+ self.auto_traversal = DEFAULT_AUTO_TRAVERSAL
78
+ self.raw_response = DEFAULT_RAW_RESPONSE
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,44 @@
1
+ require 'faraday_middleware'
2
+ require 'faraday/request/oauth2'
3
+ require 'faraday/response/raise_sage_one_exception'
4
+ require 'faraday/response/convert_sdata_to_headers'
5
+
6
+ module SageOne
7
+ # @api private
8
+ # @note Used by request.rb to make Faraday requests to the SageOne API.
9
+ module Connection
10
+ private
11
+
12
+ # @return [Faraday::Connection] configured with the headers SageOne expects
13
+ # and our required middleware stack. raw_response can be set to true to
14
+ # help with debugging.
15
+ def connection
16
+ options = {
17
+ headers: { 'Accept' => "application/json; charset=utf-8",
18
+ 'User-Agent' => user_agent,
19
+ 'Content-Type' => 'application/json' },
20
+ proxy: proxy,
21
+ ssl: { verify: false },
22
+ url: api_endpoint
23
+ }
24
+
25
+ Faraday.new(options) do |conn|
26
+ conn.request :json
27
+
28
+ conn.use FaradayMiddleware::OAuth2, access_token
29
+ conn.use FaradayMiddleware::RaiseSageOneException
30
+
31
+ unless raw_response
32
+ conn.use FaradayMiddleware::Mashify
33
+ conn.use FaradayMiddleware::ConvertSdataToHeaders
34
+ conn.use FaradayMiddleware::ParseJson
35
+ end
36
+
37
+ faraday_config_block.call(conn) if faraday_config_block
38
+
39
+ conn.adapter(adapter)
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,39 @@
1
+ module SageOne
2
+
3
+ # Custom error class for rescuing from all SageOne errors
4
+ class Error < StandardError; end
5
+
6
+ # Raised when SageOne returns a 400 HTTP status code
7
+ class BadRequest < Error; end
8
+
9
+ # Raised when SageOne returns a 401 HTTP status code
10
+ class Unauthorized < Error; end
11
+
12
+ # Raised when SageOne returns a 403 HTTP status code
13
+ class Forbidden < Error; end
14
+
15
+ # Raised when SageOne returns a 404 HTTP status code
16
+ class NotFound < Error; end
17
+
18
+ # Raised when SageOne returns a 406 HTTP status code
19
+ class NotAcceptable < Error; end
20
+
21
+ # Raised when SageOne returns a 409 HTTP status code
22
+ class Conflict < Error; end
23
+
24
+ # Raised when SageOne returns a 422 HTTP status code
25
+ class UnprocessableEntity < Error; end
26
+
27
+ # Raised when SageOne returns a 500 HTTP status code
28
+ class InternalServerError < Error; end
29
+
30
+ # Raised when SageOne returns a 501 HTTP status code
31
+ class NotImplemented < Error; end
32
+
33
+ # Raised when SageOne returns a 502 HTTP status code
34
+ class BadGateway < Error; end
35
+
36
+ # Raised when SageOne returns a 503 HTTP status code
37
+ class ServiceUnavailable < Error; end
38
+
39
+ end
@@ -0,0 +1,49 @@
1
+ require 'sage_one/connection'
2
+ require 'sage_one/request'
3
+
4
+ module SageOne
5
+
6
+ # This module helps with setting up the OAuth connection to SageOne. After the two
7
+ # step process you will have an access_token that you can store and use
8
+ # for making future API calls.
9
+ #
10
+ # @see OAuth#authorize_url Step 1 - Authorisation request
11
+ # @see #get_access_token Step 2 - Access Token Request
12
+ module OAuth
13
+
14
+ # Generates the OAuth URL for redirecting users to SageOne. You should ensure
15
+ # the your SageOne.client_id is configured before calling this method.
16
+ #
17
+ # @param [String] callback_url SageOne OAuth will pass an authorization code back to this URL.
18
+ # @return [String] The URL for you to redirect the user to SageOne.
19
+ # @example
20
+ # SageOne.authorize_url('https://example.com/auth/sageone/callback')
21
+ def authorize_url(callback_url)
22
+ params = {
23
+ client_id: client_id,
24
+ redirect_uri: callback_url,
25
+ response_type: 'code'
26
+ }
27
+ connection.build_url("/oauth/authorize/", params).to_s
28
+ end
29
+
30
+ # Returns an access token for future authentication.
31
+ #
32
+ # @param [String] code The authorisation code SageOne sent to your callback_url.
33
+ # @param [String] callback_url The callback URL you used to get the authorization code.
34
+ # @return [Hashie::Mash] Containing the access_token for you to store for making future API calls.
35
+ # @example
36
+ # # Assuming (Rails) your code is stored in params hash.
37
+ # SageOne.get_access_token(params[:code], 'https://example.com/auth/sageone/callback')
38
+ def get_access_token(code, callback_url)
39
+ params = {
40
+ client_id: client_id,
41
+ client_secret: client_secret,
42
+ grant_type: 'authorization_code',
43
+ code: code,
44
+ redirect_uri: callback_url
45
+ }
46
+ post("/oauth/token/", params)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ require 'multi_json'
2
+
3
+ module SageOne
4
+ # This helper methods in this module are used by the public api methods.
5
+ # They use the Faraday::Connection defined in connection.rb for making
6
+ # requests. Setting 'SageOne.raw_response' to true can help with debugging.
7
+ # @api private
8
+ module Request
9
+ def delete(path, options={})
10
+ request(:delete, path, options)
11
+ end
12
+
13
+ def get(path, options={})
14
+ request(:get, path, options)
15
+ end
16
+
17
+ def post(path, options={})
18
+ request(:post, path, options)
19
+ end
20
+
21
+ def put(path, options={})
22
+ request(:put, path, options)
23
+ end
24
+
25
+ private
26
+
27
+ def request(method, path, options)
28
+ options = format_datelike_objects!(options) unless options.empty?
29
+ response = connection.send(method) do |request|
30
+ case method
31
+ when :delete, :get
32
+ options.merge!('$startIndex' => options.delete(:start_index)) if options[:start_index]
33
+ request.url(path, options)
34
+ when :post, :put
35
+ request.path = path
36
+ request.body = MultiJson.dump(options) unless options.empty?
37
+ end
38
+ request.headers['Host'] = request_host if request_host
39
+ end
40
+
41
+ if raw_response
42
+ response
43
+ elsif auto_traversal && ( next_url = links(response)["next"] )
44
+ response.body + request(method, next_url, options)
45
+ else
46
+ response.body
47
+ end
48
+ end
49
+
50
+ def links(response)
51
+ links = ( response.headers["X-SData-Pagination-Links"] || "" ).split(', ').map do |link|
52
+ url, type = link.match(/<(.*?)>; rel="(\w+)"/).captures
53
+ [ type, url ]
54
+ end
55
+
56
+ Hash[ *links.flatten ]
57
+ end
58
+
59
+ def format_datelike_objects!(options)
60
+ new_opts = {}
61
+ options.map do |k,v|
62
+ if v.respond_to?(:map)
63
+ new_opts[k] = format_datelike_objects!(v)
64
+ else
65
+ new_opts[k] = v.respond_to?(:strftime) ? v.strftime("%d/%m/%Y") : v
66
+ end
67
+ end
68
+ new_opts
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module SageOne
2
+ VERSION = "0.0.1"
3
+ end
data/lib/sage_one.rb ADDED
@@ -0,0 +1,31 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'sage_one/configuration'
4
+ require 'sage_one/client'
5
+ require 'sage_one/error'
6
+
7
+
8
+ module SageOne
9
+ extend Configuration
10
+ class << self
11
+ # Alias for SageOne::Client.new
12
+ #
13
+ # @return [SageOne::Client]
14
+ def new(options={})
15
+ SageOne::Client.new(options)
16
+ end
17
+
18
+ # True if the method can be delegated to the SageOne::Client
19
+ #
20
+ # @return [Boolean]
21
+ def respond_to?(method, include_private=false)
22
+ new.respond_to?(method, include_private) || super(method, include_private)
23
+ end
24
+
25
+ # Delegate to SageOne::Client.new
26
+ def method_missing(method, *args, &block)
27
+ return super unless new.respond_to?(method)
28
+ new.send(method, *args, &block)
29
+ end
30
+ end
31
+ end
data/sage_one.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../lib/sage_one/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.add_dependency 'addressable', '~>2.3'
6
+ s.add_dependency 'faraday', '~>0.8'
7
+ s.add_dependency 'faraday_middleware', '~> 0.9'
8
+ s.add_dependency 'hashie', '~>1.2'
9
+ s.add_dependency 'multi_json', '~> 1.4'
10
+
11
+ s.add_development_dependency 'json'
12
+ s.add_development_dependency 'rake'
13
+ s.add_development_dependency 'rspec'
14
+ s.add_development_dependency 'simplecov'
15
+ s.add_development_dependency 'webmock'
16
+ s.add_development_dependency 'yard'
17
+
18
+ s.authors = ['Luke Brown', 'Chris Stainthorpe']
19
+ s.email = ['tsdbrown@gmail.com', 'chris@randomcat.co.uk']
20
+ s.name = 'sage_one'
21
+ s.platform = Gem::Platform::RUBY
22
+ s.homepage = 'https://github.com/customersure/sage_one'
23
+ s.summary = %q{Ruby wrapper for the Sage One V1 API.}
24
+ s.description = s.summary
25
+ s.rubyforge_project = s.name
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
29
+ s.require_paths = ['lib']
30
+ s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
31
+ s.version = SageOne::VERSION
32
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayMiddleware::OAuth2 do
4
+
5
+ let(:client) { SageOne.new }
6
+
7
+ before { stub_get('sales_invoices') }
8
+
9
+ context 'when the access_token is nil' do
10
+ it 'does not add an Authorization header' do
11
+ client.send(:connection).get do |request|
12
+ request.url 'sales_invoices'
13
+ expect(request.headers.keys).not_to include("Authorization")
14
+ end
15
+ a_get("sales_invoices").should have_been_made.once
16
+ a_get("sales_invoices").with(:headers => { "Authorization" => "Bearer #{nil}"}).should_not have_been_made
17
+ end
18
+ end
19
+
20
+ context 'when the access_token is set' do
21
+ let(:access_token) { 'wHeHaMwscCIEOLGQ9uIi' }
22
+ before { client.access_token = access_token }
23
+
24
+ it 'adds an Authorization header with the Bearer token' do
25
+ client.send(:connection).get('sales_invoices')
26
+ a_get("sales_invoices").with(:headers => { 'Authorization' => "Bearer #{access_token}" }).should have_been_made.once
27
+ end
28
+ end
29
+
30
+ context 'when the Authorization header has already been set' do
31
+ let(:access_token) { 'wHeHaMwscCIEOLGQ9uIi' }
32
+ let(:pre_set_access_token) { 'SuperBearer my_pre_set_token' }
33
+ before { client.access_token = access_token }
34
+
35
+ it 'leaves the set Authorization header alone' do
36
+ client.send(:connection).get do |request|
37
+ request.url 'sales_invoices'
38
+ request.headers['Authorization'] = pre_set_access_token
39
+ end
40
+ a_get("sales_invoices").with(:headers => { 'Authorization' => pre_set_access_token }).should have_been_made.once
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayMiddleware::ConvertSdataToHeaders do
4
+
5
+ def connection(include_mashify=true)
6
+ Faraday.new(url: sage_url(nil)) do |conn|
7
+ conn.request :json
8
+ conn.use FaradayMiddleware::Mashify if include_mashify
9
+ conn.use FaradayMiddleware::ConvertSdataToHeaders
10
+ conn.use FaradayMiddleware::ParseJson
11
+ conn.adapter Faraday.default_adapter
12
+ end
13
+ end
14
+
15
+ context "when on the first page"do
16
+ it "does not add a prev link" do
17
+ stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 20, 0, 20))
18
+ response = connection.get('sales_invoices')
19
+ expect(response.headers).to be_empty
20
+ end
21
+ context "and there are more results than items_per_page" do
22
+ it "adds a next link" do
23
+ stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 40, 0, 20))
24
+ response = connection.get('sales_invoices')
25
+ expect(response.headers['X-Sdata-Pagination-Links']).to_not be_nil
26
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=20')}>; rel="next"})
27
+ end
28
+ end
29
+ end
30
+
31
+ context "when on the last page" do
32
+ before { stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 40, 20, 20)) }
33
+ it "does not add a next link" do
34
+ response = connection.get('sales_invoices')
35
+ expect(response.headers).to_not include('rel="next"')
36
+ end
37
+ it "adds a prev link" do
38
+ response = connection.get('sales_invoices')
39
+ expect(response.headers['X-Sdata-Pagination-Links']).to_not be_nil
40
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=0')}>; rel="prev"})
41
+ end
42
+ context "when requesting a value in the middle of the last page" do
43
+ before { stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 40, 30, 20)) }
44
+ it "does not add a next link" do
45
+ response = connection.get('sales_invoices')
46
+ expect(response.headers).to_not include('rel="next"')
47
+ end
48
+ it "adds a prev link" do
49
+ response = connection.get('sales_invoices')
50
+ expect(response.headers['X-Sdata-Pagination-Links']).to_not be_nil
51
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=10')}>; rel="prev"})
52
+ end
53
+ end
54
+ end
55
+
56
+ context "when there are fewer results than items_per_page" do
57
+ before { stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 5, 0, 20)) }
58
+ it "adds no links" do
59
+ response = connection.get('sales_invoices')
60
+ expect(response.headers['X-Sdata-Pagination-Links']).to be_nil
61
+ end
62
+ end
63
+
64
+ context "when on a middle page" do
65
+ before { stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 20, 10, 5)) }
66
+ it "adds a prev link" do
67
+ response = connection.get('sales_invoices')
68
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=5')}>; rel="prev"})
69
+ end
70
+ it "adds a next link" do
71
+ response = connection.get('sales_invoices')
72
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=15')}>; rel="next"})
73
+ end
74
+ end
75
+
76
+ it "places the resources in the body of the response" do
77
+ stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 20, 10, 5))
78
+ response = connection(false).get('sales_invoices')
79
+ response.body.to_s.should eq(JSON.parse(sdata_fixture('sales_invoices.json', 20,10,5))['$resources'].to_s)
80
+ end
81
+
82
+ context "when there are existing query parameters" do
83
+ before { stub_get('sales_invoices?search=salad&from_date=2012-11-11').to_return(body: sdata_fixture('sales_invoices.json', 20, 10, 5)) }
84
+ it "it passes them through without modification" do
85
+ response = connection.get('sales_invoices?from_date=2012-11-11&search=salad')
86
+ expect(response.headers['X-Sdata-Pagination-Links']).to eq('<https://app.sageone.com/api/v1/sales_invoices?%24startIndex=15&from_date=2012-11-11&search=salad>; rel="next", <https://app.sageone.com/api/v1/sales_invoices?%24startIndex=5&from_date=2012-11-11&search=salad>; rel="prev"')
87
+ end
88
+ end
89
+
90
+ context 'when there is no SData' do
91
+ before { stub_get('sales_invoices/954380').to_return(body: fixture('sales_invoice.json')) }
92
+ it "does not create link headers" do
93
+ response = connection.get('sales_invoices/954380')
94
+ expect(response.headers['X-Sdata-Pagination-Links']).to be_nil
95
+ end
96
+ it "leaves the resources in the body of the response" do
97
+ response = connection(false).get('sales_invoices/954380')
98
+ response.body.to_s.should eq(JSON.parse(fixture('sales_invoice.json').read).to_s)
99
+ end
100
+ end
101
+
102
+ context 'within first page of results, but not from start_index of 0' do
103
+ before { stub_get('sales_invoices').to_return(body: sdata_fixture('sales_invoices.json', 20, 3, 5)) }
104
+ it "Adds a previous link to start_index=0" do
105
+ response = connection.get('sales_invoices')
106
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=0')}>; rel="prev"})
107
+ end
108
+ it "Adds a next link to start_index=8" do
109
+ response = connection.get('sales_invoices')
110
+ expect(response.headers['X-Sdata-Pagination-Links']).to include(%Q{<#{sage_url('sales_invoices?%24startIndex=8')}>; rel="next"})
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe FaradayMiddleware::RaiseSageOneException do
4
+ let(:client) { SageOne::Client.new }
5
+
6
+ {
7
+ 400 => SageOne::BadRequest,
8
+ 401 => SageOne::Unauthorized,
9
+ 403 => SageOne::Forbidden,
10
+ 404 => SageOne::NotFound,
11
+ 409 => SageOne::Conflict,
12
+ 422 => SageOne::UnprocessableEntity,
13
+ }.each do |status, exception|
14
+ context "when HTTP status is #{status}" do
15
+
16
+ before { stub_get('sales_invoices').to_return(:status => status) }
17
+
18
+ it "raises #{exception.name} error" do
19
+ expect { client.sales_invoices }.to raise_error(exception)
20
+ end
21
+ end
22
+ end
23
+
24
+ context "when the response body contains an error: key (i.e. from OAuth)" do
25
+ before { stub_get('sales_invoices').to_return(:status => 400, body: { error: "Unauthorised Access" }) }
26
+ it "raises an error with the error message" do
27
+ expect { client.sales_invoices }.to raise_error(SageOne::BadRequest, '{"method":"get","url":"https://app.sageone.com/api/v1/sales_invoices","status":400,"body":{"error":"Unauthorised Access"}}')
28
+ end
29
+ end
30
+
31
+ context "when the response body contains validation errors" do
32
+ before { stub_post('sales_invoices').to_return(:status => 422, body: fixture('invalid_sales_invoice.json')) }
33
+ it "includes the validation errors" do
34
+ expect { client.post('sales_invoices', {}) }.to raise_error(SageOne::UnprocessableEntity, %q{{"method":"post","url":"https://app.sageone.com/api/v1/sales_invoices","status":422,"body":{"$diagnoses":[{"$severity":"error","$dataCode":"ValidationError","$message":"blank","$source":"due_date"},{"$severity":"error","$dataCode":"ValidationError","$message":"invalid date","$source":"due_date"},{"$severity":"error","$dataCode":"ValidationError","$message":"blank","$source":"carriage_tax_code_id"},{"$severity":"error","$dataCode":"ValidationError","$message":"The type of sale associated with a product/service no longer exists. Check product/service and try again.","$source":"line_items"}]}}})
35
+ end
36
+ end
37
+
38
+ context "when the response body is nil" do
39
+ before { stub_post('sales_invoices').to_return(:status => 422, body: nil) }
40
+ it "just converts this to a blank string" do
41
+ expect { client.post('sales_invoices', {}) }.to raise_error(SageOne::UnprocessableEntity, %q{{"method":"post","url":"https://app.sageone.com/api/v1/sales_invoices","status":422,"body":""}})
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,28 @@
1
+ {
2
+ "id":568085,
3
+ "name":"Luke Brown",
4
+ "company_name":"Luke corp",
5
+ "name_and_company_name":"Luke corp (Luke Brown)",
6
+ "contact_type":{"id":1,"$key":1},
7
+ "email":"luke.brown@example.com",
8
+ "telephone":"",
9
+ "mobile":"",
10
+ "notes":"",
11
+ "tax_reference":"",
12
+ "lock_version":0,
13
+ "main_address":{"street_one":"",
14
+ "street_two":"",
15
+ "town":"",
16
+ "county":"",
17
+ "postcode":"",
18
+ "country":{"$key":null},
19
+ "$key":1167938},
20
+ "delivery_address":{"street_one":"",
21
+ "street_two":"",
22
+ "town":"",
23
+ "county":"",
24
+ "postcode":"",
25
+ "country":{"$key":null},
26
+ "$key":1167939},
27
+ "$key":568085
28
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$diagnoses": [
3
+ {
4
+ "$severity":"error",
5
+ "$dataCode":"ValidationError",
6
+ "$message":"blank",
7
+ "$source":"due_date"
8
+ },
9
+ {
10
+ "$severity":"error",
11
+ "$dataCode":"ValidationError",
12
+ "$message":"invalid date",
13
+ "$source":"due_date"
14
+ },
15
+ {
16
+ "$severity":"error",
17
+ "$dataCode":"ValidationError",
18
+ "$message":"blank",
19
+ "$source":"carriage_tax_code_id"
20
+ },
21
+ {
22
+ "$severity":"error",
23
+ "$dataCode":"ValidationError",
24
+ "$message":"The type of sale associated with a product/service no longer exists. Check product/service and try again.",
25
+ "$source":"line_items"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1 @@
1
+ {"error":"invalid_client"}
@@ -0,0 +1 @@
1
+ {"error":"invalid_grant"}
@@ -0,0 +1,4 @@
1
+ {
2
+ "access_token":"IguLPAK5VPvDSw3z4SjrkhHTyRXgnnqAPzt1mLUk",
3
+ "token_type":"bearer"
4
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "id":954380,
3
+ "invoice_number":"SI-4",
4
+ "status":{"id":1, "$key":1},
5
+ "due_date":"19/04/2013",
6
+ "date":"20/03/2013",
7
+ "void_reason":null,
8
+ "outstanding_amount":"17.0",
9
+ "total_net_amount":"14.17",
10
+ "total_tax_amount":"2.83",
11
+ "tax_scheme_period_id":30394,
12
+ "carriage":"0.0",
13
+ "carriage_tax_code":{"id":1, "$key":1},
14
+ "carriage_tax_rate_percentage":"20.0",
15
+ "contact":{"id":568085, "$key":568085},
16
+ "contact_name":"Luke corp (Luke Brown)",
17
+ "main_address":"Killer Bees",
18
+ "delivery_address":"",
19
+ "delivery_address_same_as_main":false,
20
+ "reference":"",
21
+ "notes":"future date. did email it.",
22
+ "terms_and_conditions":"",
23
+ "lock_version":0,
24
+ "line_items":[
25
+ { "id":1570294,
26
+ "description":"Some fish - herring",
27
+ "quantity":"1.0",
28
+ "unit_price":"17.0",
29
+ "net_amount":"14.17",
30
+ "tax_amount":"2.83",
31
+ "tax_code":{"id":1, "$key":1},
32
+ "tax_rate_percentage":"20.0",
33
+ "unit_price_includes_tax":true,
34
+ "ledger_account":{"id":1898509, "$key":1898509},
35
+ "product_code":"HERRING",
36
+ "product":{"id":68656, "$key":68656},
37
+ "service":{"$key":null},
38
+ "lock_version":0,
39
+ "$key":1570294
40
+ }
41
+ ],
42
+ "$key":954380
43
+ }