bookingsync-api 0.0.4 → 0.0.5

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 (24) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -1
  3. data/lib/bookingsync/api.rb +1 -0
  4. data/lib/bookingsync/api/client.rb +80 -10
  5. data/lib/bookingsync/api/client/bookings.rb +42 -2
  6. data/lib/bookingsync/api/client/inquiries.rb +23 -0
  7. data/lib/bookingsync/api/client/rentals.rb +3 -2
  8. data/lib/bookingsync/api/error.rb +1 -0
  9. data/lib/bookingsync/api/version.rb +1 -1
  10. data/lib/bookingsync/ext/resource.rb +9 -0
  11. data/spec/bookingsync/api/client/bookings_spec.rb +78 -0
  12. data/spec/bookingsync/api/client/inquiries_spec.rb +37 -0
  13. data/spec/bookingsync/api/client_spec.rb +65 -3
  14. data/spec/cassettes/BookingSync_API_Client_Bookings/_bookings/pagination/with_a_block/yields_block_with_batch_of_bookings.yml +190 -0
  15. data/spec/cassettes/BookingSync_API_Client_Bookings/_bookings/pagination/with_auto_paginate_true/returns_all_bookings_joined_from_many_requests.yml +190 -0
  16. data/spec/cassettes/BookingSync_API_Client_Bookings/_bookings/pagination/with_per_page_setting/returns_limited_number_of_bookings.yml +65 -0
  17. data/spec/cassettes/BookingSync_API_Client_Bookings/_cancel_booking/cancels_given_booking.yml +57 -0
  18. data/spec/cassettes/BookingSync_API_Client_Bookings/_create_booking/creates_a_booking.yml +63 -0
  19. data/spec/cassettes/BookingSync_API_Client_Bookings/_edit_booking/updates_given_booking_by_ID.yml +57 -0
  20. data/spec/cassettes/BookingSync_API_Client_Inquiries/_create_inquiry/creates_a_new_inquiry.yml +64 -0
  21. data/spec/cassettes/BookingSync_API_Client_Inquiries/_inquiries/returns_inquiries.yml +64 -0
  22. data/spec/cassettes/spec/cassettes/BookingSync_API_Client_Bookings/_create_booking/creates_a_booking_yml.yml +62 -0
  23. data/spec/spec_helper.rb +24 -0
  24. metadata +24 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 482e5a2b0789e895be5b708da4e0695d9f0a260f
4
- data.tar.gz: 9243586b0e24bbab147f20ecbce0b6da009de350
3
+ metadata.gz: 43eb6b12130e0e8f634f516be82b68aa771aee94
4
+ data.tar.gz: cb1438eb9863b01ba062f7d695e7f724bb8b5ec1
5
5
  SHA512:
6
- metadata.gz: 366e4825396ef6b9cfcb2596c4ac9fdab053c8b8ce0a82ecfc7c16817724b3995df63eddc5e6e386ce077188b2286f3828fbd5a23875f5f5c82035376a1f4312
7
- data.tar.gz: ae0492b2ab4c56881ba3c225357cd72fd24d981ca94f2e6d602f1236d8914a1e4c0a65e61cfba93efe9312565ac7ebf0eb15d6be2059a4d28e4e3c4e4e72f939
6
+ metadata.gz: b0236f9a753191bb68adc74a0074acd258901ad0f37fd1e5ea78ffa2ce658fe1056482480e1b53abd1cb7dbd0aa6014ed586c45ee4c67114afaae3af1a4411c3
7
+ data.tar.gz: 65eaa193e9b8f89a24a0192486554313f16462494327602966b32efc649eba4e720acb06126d70c175b13e9cf0146a1ba9b82c2455f23885dd4ac3b50b286136
data/README.md CHANGED
@@ -27,7 +27,31 @@ Gem assumes that you already have OAuth token for an account.
27
27
  rentals = api.rentals # => [Sawyer::Resource, Sawyer::Resource]
28
28
  rentals.first.name # => "Small apartment"
29
29
 
30
- See our [documentation](http://rubydoc.info/github/BookingSync/bookingsync-api) for more info.
30
+ ### Pagination
31
+
32
+ All endpoints returning a collection of resources can be paginated. There are three ways to do it.
33
+
34
+ Specify `:per_page` and `:page` params. It's useful when implementing pagination on your site.
35
+
36
+ api.bookings(per_page: 10, page: 1) => [Sawyer::Resource, Sawyer::Resource, ...]
37
+
38
+ Use pagination with a block.
39
+
40
+ api.bookings(per_page: 10) do |batch|
41
+ # display 10 bookings, will make one HTTP request for each batch
42
+ end
43
+
44
+ Fetch all resources (with multiple requests under the hood) and return one big array.
45
+
46
+ api.bookings(auto_paginate: true) => [Sawyer::Resource, Sawyer::Resource, ...]
47
+
48
+ ## Gem documentation
49
+
50
+ See [gem documentation](http://rdoc.info/github/BookingSync/bookingsync-api/master/frames) for more info.
51
+
52
+ ## API documentation
53
+
54
+ See [API documentation](http://docs.api.bookingsync.com).
31
55
 
32
56
  ## Running specs
33
57
 
@@ -1,6 +1,7 @@
1
1
  require "faraday"
2
2
  require "sawyer"
3
3
 
4
+ require "bookingsync/ext/resource"
4
5
  require "bookingsync/api/version"
5
6
  require "bookingsync/api/client"
6
7
 
@@ -1,11 +1,13 @@
1
1
  require "bookingsync/api/middleware/authentication"
2
2
  require "bookingsync/api/client/bookings"
3
+ require "bookingsync/api/client/inquiries"
3
4
  require "bookingsync/api/client/rentals"
4
5
  require "bookingsync/api/error"
5
6
 
6
7
  module BookingSync::API
7
8
  class Client
8
9
  include BookingSync::API::Client::Bookings
10
+ include BookingSync::API::Client::Inquiries
9
11
  include BookingSync::API::Client::Rentals
10
12
 
11
13
  MEDIA_TYPE = "application/vnd.api+json"
@@ -23,9 +25,37 @@ module BookingSync::API
23
25
  # Make a HTTP GET request
24
26
  #
25
27
  # @param path [String] The path, relative to {#api_endpoint}
28
+ # @param options [Hash] Query params for the request
26
29
  # @return [Array<Sawyer::Resource>] Array of resources.
27
30
  def get(path, options = {})
28
- request :get, path, options
31
+ request :get, path, query: options
32
+ end
33
+
34
+ # Make a HTTP POST request
35
+ #
36
+ # @param path [String] The path, relative to {#api_endpoint}
37
+ # @param options [Hash] Body params for the request
38
+ # @return [Array<Sawyer::Resource>]
39
+ def post(path, options = {})
40
+ request :post, path, options
41
+ end
42
+
43
+ # Make a HTTP PUT request
44
+ #
45
+ # @param path [String] The path, relative to {#api_endpoint}
46
+ # @param options [Hash] Body params for the request
47
+ # @return [Array<Sawyer::Resource>]
48
+ def put(path, options = {})
49
+ request :put, path, options
50
+ end
51
+
52
+ # Make a HTTP DELETE request
53
+ #
54
+ # @param path [String] The path, relative to {#api_endpoint}
55
+ # @param options [Hash] Body params for the request
56
+ # @return [Array<Sawyer::Resource>]
57
+ def delete(path, options = {})
58
+ request :delete, path, options
29
59
  end
30
60
 
31
61
  # Return API endpoint
@@ -41,28 +71,68 @@ module BookingSync::API
41
71
  #
42
72
  # @param method [Symbol] HTTP verb to use.
43
73
  # @param path [String] The path, relative to {#api_endpoint}.
44
- # @param query [Hash] A customizable set of query options.
74
+ # @param data [Hash] Data to be send in the request's body
75
+ # it can include query: key with requests params for GET requests
76
+ # @param options [Hash] A customizable set of request options.
45
77
  # @return [Array<Sawyer::Resource>] Array of resources.
46
- def request(method, path, query = {})
47
- [:fields, :status].each do |key|
48
- query[key] = Array(query[key]).join(",") if query.has_key?(key)
78
+ def request(method, path, data, options = {})
79
+ if data.is_a?(Hash)
80
+ options[:query] = data.delete(:query) || {}
81
+ options[:query].keys.each do |key|
82
+ if options[:query][key].is_a?(Array)
83
+ options[:query][key] = options[:query][key].join(",")
84
+ end
85
+ end
49
86
  end
50
87
 
51
- response = agent.call(method, path, nil, {query: query})
88
+ @last_response = response = agent.call(method, path, data.to_json, options)
52
89
  case response.status
53
- # fetch objects from outer hash
54
- # {rentals => [{rental}, {rental}]}
55
- # will return [{rental}, {rental}]
56
- when 200..299; response.data.to_hash.values.flatten
90
+ when 204; [] # update/destroy response
91
+ when 200..299; json_api_to_array(response.data)
57
92
  when 401; raise Unauthorized.new
93
+ when 422; raise UnprocessableEntity.new
58
94
  end
59
95
  end
60
96
 
97
+ def paginate(path, options = {}, &block)
98
+ auto_paginate = options.delete(:auto_paginate)
99
+
100
+ data = request(:get, path, query: options)
101
+
102
+ if (block_given? or auto_paginate) && @last_response.rels[:next]
103
+ first_request = true
104
+ loop do
105
+ if block_given?
106
+ yield(json_api_to_array(@last_response.data))
107
+ elsif auto_paginate
108
+ data.concat(json_api_to_array(@last_response.data)) unless first_request
109
+ first_request = false
110
+ end
111
+ break unless @last_response.rels[:next]
112
+ @last_response = @last_response.rels[:next].get
113
+ end
114
+ end
115
+
116
+ data
117
+ end
118
+
61
119
  private
62
120
 
121
+ # Return collection of resources
122
+ #
123
+ # In jsonapi spec every response has format
124
+ # {resources => [{resource}, {resource}]. This method returns the inner Array
125
+ # @param data [Sawyer::Resource] Sawyer resource from response.data
126
+ # @return [<Sawyer::Resource>] An Array of resources
127
+ # FIXME: This could have better name
128
+ def json_api_to_array(data)
129
+ data.to_hash.values.flatten
130
+ end
131
+
63
132
  def agent
64
133
  @agent ||= Sawyer::Agent.new(api_endpoint, sawyer_options) do |http|
65
134
  http.headers[:accept] = MEDIA_TYPE
135
+ http.headers[:content_type] = MEDIA_TYPE
66
136
  end
67
137
  end
68
138
 
@@ -15,11 +15,51 @@ module BookingSync::API
15
15
  # are shown, otherwise they are hidden.
16
16
  # @option options [Array] status: Array of booking states.
17
17
  # If specyfied bookings with given states are shown.
18
+ # Possible statuses: `:booked`, `:unavailable` and `:tentative`
18
19
  # @return [Array<Sawyer::Resource>] Array of bookings.
19
20
  # @example
20
21
  # @api.bookings(months: 12, states: [:booked, :unavailable], include_canceled: true)
21
- def bookings(options = {})
22
- get :bookings, options
22
+ #
23
+ # @example Pagination
24
+ # @api.bookings(per_page: 10) do |batch|
25
+ # # do something with ten bookings
26
+ # end
27
+ # @see http://docs.api.bookingsync.com/reference/endpoints/bookings/#list-bookings
28
+ # @see http://docs.api.bookingsync.com/reference/endpoints/bookings/#search-bookings
29
+ def bookings(options = {}, &block)
30
+ paginate :bookings, options, &block
31
+ end
32
+
33
+
34
+ # Create a booking
35
+ #
36
+ # @param options [Hash] Booking attributes
37
+ # @return <Sawyer::Resource> Newly create booking
38
+ def create_booking(options = {})
39
+ post(:bookings, bookings: [options]).pop
40
+ end
41
+
42
+ # Edit a booking
43
+ #
44
+ # @param booking [Sawyer::Resource|Integer] Booking or ID of the booking
45
+ # to be updated
46
+ # @param options [Hash] Booking attributes to be updated
47
+ # FIXME: should be changed resource
48
+ # @return [Array] An empty Array on success, exception is raised otherwise
49
+ # @example
50
+ # booking = @api.bookings.first
51
+ # @api.edit_booking(booking, {adults: 1}) => []
52
+ def edit_booking(booking, options = {})
53
+ put "bookings/#{booking}", bookings: [options]
54
+ end
55
+
56
+ # Cancel a booking
57
+ #
58
+ # @param booking [Sawyer::Resource|Integer] Booking or ID of the booking
59
+ # to be canceled
60
+ # @return [Array] An empty Array on success, exception is raised otherwise
61
+ def cancel_booking(booking, options = {})
62
+ delete "bookings/#{booking}"
23
63
  end
24
64
  end
25
65
  end
@@ -0,0 +1,23 @@
1
+ module BookingSync::API
2
+ class Client
3
+ module Inquiries
4
+ # List inquiries
5
+ #
6
+ # Return list of inquiries for current account.
7
+ # @param options [Hash] A customizable set of query options.
8
+ # @return [Array<Sawyer::Resource>] Array of inquiries.
9
+ # @see http://docs.api.bookingsync.com/reference/endpoints/inquiries/#list-inquiries
10
+ def inquiries(options = {}, &block)
11
+ paginate :inquiries, options, &block
12
+ end
13
+
14
+ # Create a new inquiry
15
+ #
16
+ # @param options [Hash] Inquiry attributes
17
+ # @return <Sawyer::Resource> Newly create inquiry
18
+ def create_inquiry(options = {})
19
+ post(:inquiries, inquiries: [options]).pop
20
+ end
21
+ end
22
+ end
23
+ end
@@ -13,8 +13,9 @@ module BookingSync::API
13
13
  # rentals.first.name # => "Small apartment"
14
14
  # @example Get the list of rentals only with name and description for smaller response
15
15
  # @api.rentals(fields: [:name, :description])
16
- def rentals(options = {})
17
- get :rentals, options
16
+ # @see http://docs.api.bookingsync.com/reference/endpoints/rentals/#list-rentals
17
+ def rentals(options = {}, &block)
18
+ paginate :rentals, options, &block
18
19
  end
19
20
  end
20
21
  end
@@ -2,4 +2,5 @@ module BookingSync::API
2
2
  # Class for rescuing all BS API errors
3
3
  class Error < StandardError; end
4
4
  class Unauthorized < Error; end
5
+ class UnprocessableEntity < Error; end
5
6
  end
@@ -1,5 +1,5 @@
1
1
  module BookingSync
2
2
  module API
3
- VERSION = "0.0.4"
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -0,0 +1,9 @@
1
+ module Sawyer
2
+ class Resource
3
+ # With below alias it's possible to pass ID or Sawyer::Resource
4
+ # to edit/destroy methods
5
+ def to_s
6
+ id
7
+ end
8
+ end
9
+ end
@@ -8,5 +8,83 @@ describe BookingSync::API::Client::Bookings do
8
8
  expect(client.bookings).not_to be_nil
9
9
  assert_requested :get, bs_url("bookings")
10
10
  end
11
+
12
+ describe "pagination" do
13
+ context "with per_page setting" do
14
+ it "returns limited number of bookings" do
15
+ bookings = client.bookings(per_page: 2)
16
+ expect(bookings.size).to eql(2)
17
+ end
18
+ end
19
+
20
+ context "with a block" do
21
+ it "yields block with batch of bookings" do
22
+ sizes = [2, 2, 1]
23
+ index = 0
24
+ client.bookings(per_page: 2) do |bookings|
25
+ expect(bookings.size).to eql(sizes[index])
26
+ index += 1
27
+ end
28
+ end
29
+ end
30
+
31
+ context "with auto_paginate: true" do
32
+ it "returns all bookings joined from many requests" do
33
+ bookings = client.bookings(per_page: 2, auto_paginate: true)
34
+ expect(bookings.size).to eql(5)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ describe ".create_booking", :vcr do
41
+ let(:attributes) {
42
+ {start_at: '2014-01-03', end_at: '2014-01-04', rental_id: 20,
43
+ booked: true}
44
+ }
45
+
46
+ it "creates a booking" do
47
+ client.create_booking(attributes)
48
+ assert_requested :post, bs_url("bookings"),
49
+ body: {bookings: [attributes]}.to_json
50
+ end
51
+
52
+ it "returns newly created booking" do
53
+ VCR.use_cassette('BookingSync_API_Client_Bookings/_create_booking/creates_a_booking') do
54
+ booking = client.create_booking(attributes)
55
+ expect(booking.account_id).to eql(1)
56
+ expect(booking.rental_id).to eql(20)
57
+ end
58
+ end
59
+ end
60
+
61
+ describe ".edit_booking", :vcr do
62
+ it "updates given booking by ID" do
63
+ client.edit_booking(50, {adults: 1})
64
+ assert_requested :put, bs_url("bookings/50"),
65
+ body: {bookings: [{adults: 1}]}.to_json
66
+ end
67
+
68
+ it "returns an empty array" do
69
+ VCR.use_cassette('BookingSync_API_Client_Bookings/_edit_booking/updates_given_booking_by_ID') do
70
+ expect(client.edit_booking(50, {adults: 1})).to eql([])
71
+ end
72
+ end
73
+
74
+ it "updates given booking by Resource object" do
75
+ VCR.use_cassette('BookingSync_API_Client_Bookings/_edit_booking/updates_given_booking_by_ID') do
76
+ resource = double(to_s: "50")
77
+ client.edit_booking(resource, {adults: 1})
78
+ assert_requested :put, bs_url("bookings/50"),
79
+ body: {bookings: [{adults: 1}]}.to_json
80
+ end
81
+ end
82
+ end
83
+
84
+ describe ".cancel_booking", :vcr do
85
+ it "cancels given booking" do
86
+ client.cancel_booking(50)
87
+ assert_requested :delete, bs_url("bookings/50")
88
+ end
11
89
  end
12
90
  end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe BookingSync::API::Client::Inquiries do
4
+ let(:client) { BookingSync::API::Client.new(test_access_token) }
5
+
6
+ describe ".inquiries", :vcr do
7
+ it "returns inquiries" do
8
+ expect(client.inquiries).not_to be_nil
9
+ assert_requested :get, bs_url("inquiries")
10
+ end
11
+ end
12
+
13
+ describe ".create_inquiry", :vcr do
14
+ let(:attributes) { {
15
+ rental_id: 7,
16
+ start_at: Time.now,
17
+ end_at: Time.now + 86400, # one day
18
+ firstname: "John",
19
+ lastname: "Smith",
20
+ email: "john@example.com"
21
+ } }
22
+
23
+ it "creates a new inquiry" do
24
+ client.create_inquiry(attributes)
25
+ assert_requested :post, bs_url("inquiries"),
26
+ body: {inquiries: [attributes]}.to_json
27
+ end
28
+
29
+ it "returns newly created inquiry" do
30
+ VCR.use_cassette('BookingSync_API_Client_Inquiries/_create_inquiry/creates_a_new_inquiry') do
31
+ inquiry = client.create_inquiry(attributes)
32
+ expect(inquiry.rental_id).to eql(7)
33
+ expect(inquiry.firstname).to eql("John")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -17,6 +17,48 @@ describe BookingSync::API::Client do
17
17
  client.get("resource")
18
18
  assert_requested :get, bs_url("resource")
19
19
  end
20
+
21
+ it "adds query options to the URL" do
22
+ stub_get("resource?abc=123")
23
+ client.get("resource", abc: 123)
24
+ assert_requested :get, bs_url("resource?abc=123")
25
+ end
26
+ end
27
+
28
+ describe "#post" do
29
+ before { VCR.turn_off! }
30
+ it "makes a HTTP POST request with body" do
31
+ stub_post("resource")
32
+ client.post("resource", {key: :value})
33
+ assert_requested :post, bs_url("resource"), body: '{"key":"value"}'
34
+ end
35
+
36
+ context "on 422 response" do
37
+ it "raises UnprocessableEntity exception" do
38
+ stub_post("resource", status: 422)
39
+ expect {
40
+ client.post("resource", {key: :value})
41
+ }.to raise_error(BookingSync::API::UnprocessableEntity)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#put" do
47
+ before { VCR.turn_off! }
48
+ it "makes a HTTP PUT request with body" do
49
+ stub_put("resource")
50
+ client.put("resource", {key: :value})
51
+ assert_requested :put, bs_url("resource"), body: '{"key":"value"}'
52
+ end
53
+ end
54
+
55
+ describe "#delete" do
56
+ before { VCR.turn_off! }
57
+ it "makes a HTTP DELETE request" do
58
+ stub_delete("resource")
59
+ client.delete("resource")
60
+ assert_requested :delete, bs_url("resource")
61
+ end
20
62
  end
21
63
 
22
64
  describe "#request" do
@@ -29,13 +71,20 @@ describe BookingSync::API::Client do
29
71
  headers: {"Authorization" => "Bearer fake-access-token"}
30
72
  end
31
73
 
32
- it "requests proper content type for JSON API" do
74
+ it "requests proper accept header for JSON API" do
33
75
  stub_get("resource")
34
76
  client.get("resource")
35
77
  assert_requested :get, bs_url("resource"),
36
78
  headers: {"Accept" => "application/vnd.api+json"}
37
79
  end
38
80
 
81
+ it "requests sends data with JSON API content type" do
82
+ stub_post("resource")
83
+ client.post("resource")
84
+ assert_requested :post, bs_url("resource"),
85
+ headers: {"Content-Type" => "application/vnd.api+json"}
86
+ end
87
+
39
88
  it "returns Array of resources" do
40
89
  stub_get("resource", body: {resources: [{name: "Megan"}]}.to_json)
41
90
  resources = client.get("resource")
@@ -43,7 +92,7 @@ describe BookingSync::API::Client do
43
92
  expect(resources.first.name).to eq("Megan")
44
93
  end
45
94
 
46
- context "client returns 401" do
95
+ context "API returns 401" do
47
96
  it "raises Unauthorized exception" do
48
97
  stub_get("resource", status: 401)
49
98
  expect {
@@ -52,19 +101,32 @@ describe BookingSync::API::Client do
52
101
  end
53
102
  end
54
103
 
55
- context "status code is outside 200..299 range" do
104
+ context "API returns status code outside 200..299 range" do
56
105
  it "returns nil" do
57
106
  stub_get("resource", status: 404)
58
107
  expect(client.get("resource")).to be_nil
59
108
  end
60
109
  end
61
110
 
111
+ context "API returns 204 No Content" do
112
+ it "returns an empty array" do
113
+ stub_get("resource", status: 204)
114
+ expect(client.get("resource")).to eql([])
115
+ end
116
+ end
117
+
62
118
  context "user wants to fetch only specific fields" do
63
119
  it "constructs url for filtered fields" do
64
120
  stub_get("resource?fields=name,description")
65
121
  client.get("resource", fields: [:name, :description])
66
122
  assert_requested :get, bs_url("resource?fields=name,description")
67
123
  end
124
+
125
+ it "should support single field" do
126
+ stub_get("resource?fields=name")
127
+ client.get("resource", fields: :name)
128
+ assert_requested :get, bs_url("resource?fields=name")
129
+ end
68
130
  end
69
131
 
70
132
  context "user passes additional query options" do