kookaburra 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5cadefaae85409b2b4db57ce339eea29091d9a87
4
- data.tar.gz: 14ca8365ce3aecd80dbf5a78a8e7581d1461dac1
3
+ metadata.gz: 0096d882dd0e4a30ea2ff6f38744a307ca77b596
4
+ data.tar.gz: 97dd32061c90235865ed398517c59c356d0b3b38
5
5
  SHA512:
6
- metadata.gz: b4ca6636a527e1a6de7af7b92ac9c87be2ca3ab507b5327ed4513635a522a3e794aff3930720cfbde751ad7dbb0236e52e9e5697f7a1f57569006a4ad6d7d355
7
- data.tar.gz: a2badf5f817a513f45c4955bdb3ea0b9a8139a08fd4826fba07d43be6a8d11a0d9c8c07eb6a296904c562c22b556c834a1edba366953f2aa122a2af8240821d4
6
+ metadata.gz: 2022e2a53bef18d607ada46e5043d2f17c79f4699f4ee02b5d06c4b12c9d219727fee3bb2cee84e55383a35697d2918552a295b49e165d13bc0505a311c59097
7
+ data.tar.gz: 86883520f7835ebcd750f01448d39727ffa48c9f8c8d564b5b8ca42bd12e17468d6f7ee11aeb958a398f1facf939a09bb108f2c3e7bc6e9e68f67c246664d047
data/.travis.yml CHANGED
@@ -2,8 +2,8 @@ before_install:
2
2
  - "export DISPLAY=:99.0"
3
3
  - "sh -e /etc/init.d/xvfb start"
4
4
  rvm:
5
+ - "2.1.2"
5
6
  - "2.0.0"
6
7
  - "1.9.3"
7
- - "jruby-19mode"
8
8
  env:
9
9
  - "DISPLAY=:99.0"
data/README.markdown CHANGED
@@ -37,7 +37,7 @@ running.
37
37
 
38
38
  The fact that Kookaburra runs against a remote server means that *it is not
39
39
  limited to testing only Ruby web applications*. As long as your application
40
- exposes a web-service API for use by the GivenDriver and an HTML user interface
40
+ exposes a web-service API for use by the APIDriver and an HTML user interface
41
41
  for use by the UIDriver, you can use Kookaburra to test it. Also, as long as
42
42
  you're careful with both your application and test designs, you're not limited
43
43
  to running your tests only in an isolated testing environment; you could run
@@ -53,19 +53,19 @@ stopping your server for you. Examples of how to do so with a Rack application
53
53
  are presented below, but you should be able to take the same basic approach with
54
54
  other types of application servers.
55
55
 
56
- Although Capybara is capable of starting a Rack application server on its own,
57
- the default setup only starts the server up on-demand when you call a method
58
- that requires the browser to interact with the web application. Because the
59
- APIDriver layer does not use Capybara, it is necessary to manage the server
60
- process on your own. Otherwise the server would not be guaranteed to be running
61
- when you call the APIDriver methods (particularly as these often appear in
62
- "Given" statements that are run before you start interacting with the web
63
- browser.)
56
+ Although Capybara is capable of starting a Rack application server on
57
+ its own, the default setup only starts the server up on-demand when you
58
+ call a method that requires the browser to interact with the web
59
+ application. Because the APIClient layer does not use Capybara, it is
60
+ necessary to manage the server process on your own. Otherwise the server
61
+ would not be guaranteed to be running when you call the APIClient
62
+ methods (particularly as these often appear in "Given" statements that
63
+ are run before you start interacting with the web browser.)
64
64
 
65
65
  Keep in mind that, even if your server is capable of being started up in another
66
66
  thread within the same Ruby process that is executing your test suite, you will
67
67
  want to avoid doing so unless you are using a Ruby interpreter that supports
68
- native threads. Otherwise, when the APIDriver makes an HTTP call to your
68
+ native threads. Otherwise, when the APIClient makes an HTTP call to your
69
69
  application's API, it will block while waiting for a response, thus preventing
70
70
  your application from being able to respond to that request and resulting in a
71
71
  timeout error in your tests.
@@ -83,15 +83,15 @@ add the following to `spec/support/kookaburra_setup.rb`:
83
83
 
84
84
  require 'kookaburra/test_helpers'
85
85
 
86
- # Change these to the files that define your custom GivenDriver and UIDriver
86
+ # Change these to the files that define your custom APIDriver and UIDriver
87
87
  # implementations.
88
- require 'my_app/kookaburra/given_driver'
88
+ require 'my_app/kookaburra/api_driver'
89
89
  require 'my_app/kookaburra/ui_driver'
90
90
 
91
91
  # c.app_host below should be set to whatever the root URL of your running
92
92
  # application is.
93
93
  Kookaburra.configure do |c|
94
- c.given_driver_class = MyApp::Kookaburra::GivenDriver
94
+ c.api_driver_class = MyApp::Kookaburra::APIDriver
95
95
  c.ui_driver_class = MyApp::Kookaburra::UIDriver
96
96
  c.app_host = 'http://my_app.example.com:1234'
97
97
  c.browser = Capybara::Session.new(:selenium)
@@ -101,7 +101,7 @@ add the following to `spec/support/kookaburra_setup.rb`:
101
101
  end
102
102
 
103
103
  RSpec.configure do |c|
104
- # Makes the #k, #given and #ui methods available to your specs
104
+ # Makes the #k, #api and #ui methods available to your specs
105
105
  # (See section on test implementation below)
106
106
  c.include(Kookaburra::TestHelpers, :type => :request)
107
107
  end
@@ -117,9 +117,9 @@ and shut down a Rack application server. Just add the following to
117
117
  require 'kookaburra/test_helpers'
118
118
  require 'kookaburra/rack_app_server'
119
119
 
120
- # Change these to the files that define your custom GivenDriver and UIDriver
120
+ # Change these to the files that define your custom APIDriver and UIDriver
121
121
  # implementations.
122
- require 'my_app/kookaburra/given_driver'
122
+ require 'my_app/kookaburra/api_driver'
123
123
  require 'my_app/kookaburra/ui_driver'
124
124
 
125
125
  # `MyApplication` below should be replaced with the object that
@@ -134,7 +134,7 @@ and shut down a Rack application server. Just add the following to
134
134
  # c.app_host below should be set to whatever the root URL of your
135
135
  # running application is.
136
136
  Kookaburra.configure do |c|
137
- c.given_driver_class = MyApp::Kookaburra::GivenDriver
137
+ c.api_driver_class = MyApp::Kookaburra::APIDriver
138
138
  c.ui_driver_class = MyApp::Kookaburra::UIDriver
139
139
  c.app_host = 'http://localhost:%d' % app_server.port
140
140
  c.browser = Capybara::Session.new(:selenium)
@@ -168,15 +168,15 @@ add the following to `features/support/kookaburra_setup.rb`:
168
168
 
169
169
  require 'kookaburra/test_helpers'
170
170
 
171
- # Change these to the files that define your custom GivenDriver and UIDriver
171
+ # Change these to the files that define your custom APIDriver and UIDriver
172
172
  # implementations.
173
- require 'my_app/kookaburra/given_driver'
173
+ require 'my_app/kookaburra/api_driver'
174
174
  require 'my_app/kookaburra/ui_driver'
175
175
 
176
176
  # c.app_host below should be set to whatever the root URL of your running
177
177
  # application is.
178
178
  Kookaburra.configure do |c|
179
- c.given_driver_class = MyApp::Kookaburra::GivenDriver
179
+ c.api_driver_class = MyApp::Kookaburra::APIDriver
180
180
  c.ui_driver_class = MyApp::Kookaburra::UIDriver
181
181
  c.app_host = 'http://my_app.example.com:1234'
182
182
  c.browser = Capybara::Session.new(:selenium)
@@ -198,9 +198,9 @@ and shut down a Rack application server. Just add the following to
198
198
  require 'kookaburra/test_helpers'
199
199
  require 'kookaburra/rack_app_server'
200
200
 
201
- # Change these to the files that define your custom GivenDriver and UIDriver
201
+ # Change these to the files that define your custom APIDriver and UIDriver
202
202
  # implementations.
203
- require 'my_app/kookaburra/given_driver'
203
+ require 'my_app/kookaburra/api_driver'
204
204
  require 'my_app/kookaburra/ui_driver'
205
205
 
206
206
  # `MyApplication` below should be replaced with the object that
@@ -215,7 +215,7 @@ and shut down a Rack application server. Just add the following to
215
215
  # c.app_host below should be set to whatever the root URL of your
216
216
  # running application is.
217
217
  Kookaburra.configure do |c|
218
- c.given_driver_class = MyApp::Kookaburra::GivenDriver
218
+ c.api_driver_class = MyApp::Kookaburra::APIDriver
219
219
  c.ui_driver_class = MyApp::Kookaburra::UIDriver
220
220
  c.app_host = 'http://localhost:%d' % app_server.port
221
221
  c.browser = Capybara::Session.new(:selenium)
@@ -243,9 +243,9 @@ the following layers:
243
243
  spcification documents)
244
244
  2. The **Test Implementation** (Cucumber step definitions, RSpec example blocks,
245
245
  etc.)
246
- 3. The **Domain Driver** (Kookaburra::GivenDriver and Kookaburra::UIDriver)
246
+ 3. The **Domain Driver** (Kookaburra::APIDriver and Kookaburra::UIDriver)
247
247
  4. The **Window Driver** (Kookaburra::UIDriver::UIComponent)
248
- 5. The **Application Driver** (Capybara and Kookaburra::APIDriver)
248
+ 5. The **Application Driver** (Capybara and Kookaburra::APIClient)
249
249
 
250
250
  ### The Business Specification Language ###
251
251
 
@@ -315,19 +315,19 @@ might look:
315
315
  # step_definitions/various_steps.rb
316
316
 
317
317
  Given "I have an existing account" do
318
- given.existing_account
318
+ api.existing_account
319
319
  end
320
320
 
321
321
  Given "I have previously specified default payment options" do
322
- given.default_payment_options_specified
322
+ api.default_payment_options_specified
323
323
  end
324
324
 
325
325
  Given "I have previously specified default shipping options" do
326
- given.default_shipping_options_specified
326
+ api.default_shipping_options_specified
327
327
  end
328
328
 
329
329
  Given "I have an item in my shopping cart" do
330
- given.an_item_in_my_shopping_cart
330
+ api.an_item_in_my_shopping_cart
331
331
  end
332
332
 
333
333
  When "I sign in to my account" do
@@ -389,10 +389,10 @@ Using RSpec, the test implementation would be as follows:
389
389
 
390
390
  describe "Purchase Items in Cart" do
391
391
  example "Using Existing Billing and Shipping Information" do
392
- given.existing_account(:my_account)
393
- given.default_payment_options_specified_for(:my_account)
394
- given.default_shipping_options_specified_for(:my_account)
395
- given.an_item_in_my_shopping_cart(:my_account)
392
+ api.existing_account(:my_account)
393
+ api.default_payment_options_specified_for(:my_account)
394
+ api.default_shipping_options_specified_for(:my_account)
395
+ api.an_item_in_my_shopping_cart(:my_account)
396
396
 
397
397
  ui.sign_in(:my_account)
398
398
  ui.choose_to_check_out
@@ -407,21 +407,21 @@ Using RSpec, the test implementation would be as follows:
407
407
 
408
408
  The Domain Driver layer is where you build up an internal DSL that describes the
409
409
  business concepts of your application at a fairly high level. It consists of two
410
- top-level drivers: the `GivenDriver` (available via `#given`) used to set up
410
+ top-level drivers: the `APIDriver` (available via `#api`) used to set up
411
411
  state for your tests and the UIDriver (available via `#ui`) for describing the
412
412
  tasks that a user can accomplish with the application.
413
413
 
414
414
  #### Mental Model ####
415
415
 
416
- `Kookaburra::MentalModel` is the component via which the `GivenDriver` and the
416
+ `Kookaburra::MentalModel` is the component via which the `APIDriver` and the
417
417
  `UIDriver` share information, and it is intended to represent your application
418
418
  user's mental picture of the data they are working with. For instance, if you
419
- create a user account via the `GivenDriver`, you would store the login
419
+ create a user account via the `APIDriver`, you would store the login
420
420
  credentials for that account in the `MentalModel` instance, so the `UIDriver`
421
421
  knows what to use when you tell it to `#sign_in`. This is what allows the
422
422
  Cucumber step definitions to remain free from explicitly shared state.
423
423
 
424
- Kookaburra automatically configures your `GivenDriver` and your `UIDriver` to
424
+ Kookaburra automatically configures your `APIDriver` and your `UIDriver` to
425
425
  share a `MentalModel` instance, which is available to both of them via their
426
426
  `#mental_model` method.
427
427
 
@@ -459,20 +459,20 @@ Here's an example of MentalModel behavior:
459
459
  mental_model.widgets.deleted[:widget_a]
460
460
  #=> {'name' => 'Widget A'}
461
461
 
462
- #### Given Driver ####
462
+ #### API Driver ####
463
463
 
464
- The `Kookaburra::GivenDriver` is used to create a particular "preexisting" state
464
+ The `Kookaburra::APIDriver` is used to create a particular "preexisting" state
465
465
  within your application's data and ensure you have a handle to that data (when
466
466
  needed) prior to interacting with the UI. You will create a subclass of
467
- `Kookaburra::GivenDriver` in which you will create part of the Domain Driver DSL
467
+ `Kookaburra::APIDriver` in which you will create part of the Domain Driver DSL
468
468
  for your application:
469
469
 
470
- # lib/my_app/kookaburra/given_driver.rb
470
+ # lib/my_app/kookaburra/api_driver.rb
471
471
 
472
- class MyApp::Kookaburra::GivenDriver < Kookaburra::GivenDriver
473
- # Specify the APIDriver to use
472
+ class MyApp::Kookaburra::APIDriver < Kookaburra::APIDriver
473
+ # Specify the APIClient to use
474
474
  def api
475
- @api ||= MyApp::Kookaburra::APIDriver.new(configuration)
475
+ @api ||= MyApp::Kookaburra::APIClient.new(configuration)
476
476
  end
477
477
 
478
478
  def existing_account(nickname)
@@ -489,24 +489,17 @@ for your application:
489
489
  end
490
490
  end
491
491
 
492
- Although there is nothing that actually *prevents* you from interacting with the
493
- UI in the `GivenDriver`, you should avoid doing so. The `GivenDriver`'s purpose
494
- is to describe state that exists *before* the user interaction that is being
495
- tested. Although this state may be the result of a previous user interaction,
496
- your tests will be much, much faster if you create this state via API calls
497
- rather than driving a web browser.
492
+ #### API Client ####
498
493
 
499
- #### API Driver ####
500
-
501
- The `Kookaburra::APIDriver` is used to interact with an application's
494
+ The `Kookaburra::APIClient` is used to interact with an application's
502
495
  external web services API. You tell Kookaburra about your API by
503
- creating a subclass of `Kookaburra::APIDriver` for your application,
496
+ creating a subclass of `Kookaburra::APIClient` for your application,
504
497
  specifying how requests should be encoded and decoded, and specifying
505
498
  any headers that should be present on every request.
506
499
 
507
500
  # lib/my_app/kookaburra/api_driver.rb
508
501
 
509
- class MyApp::Kookaburra::APIDriver < Kookaburra::APIDriver
502
+ class MyApp::Kookaburra::APIClient < Kookaburra::APIClient
510
503
  encode_with { |data| JSON.dump(data) }
511
504
  decode_with { |data| JSON.parse(data) }
512
505
  header 'Content-Type', 'application/json'
@@ -521,7 +514,7 @@ any headers that should be present on every request.
521
514
  end
522
515
  end
523
516
 
524
- The content of your application's APIDriver should consist mainly of
517
+ The content of your application's APIClient should consist mainly of
525
518
  mappings between discrete actions and HTTP requests to the specified URL
526
519
  paths.
527
520
 
@@ -548,7 +541,7 @@ within your subclass:
548
541
 
549
542
  ### The Window Driver Layer ###
550
543
 
551
- While your `GivenDriver` and `UIDriver` provide a DSL that represents actions
544
+ While your `APIDriver` and `UIDriver` provide a DSL that represents actions
552
545
  your users can perform in your application, the [Window Driver] [Window Driver]
553
546
  layer describes the individual user interface components that the user interacts
554
547
  with to perform these tasks. By describing each interface component using an OOP
@@ -601,9 +594,9 @@ You describe the various user interface components by sub-classing
601
594
 
602
595
  ### The Application Driver Layer ###
603
596
 
604
- `Kookaburra::APIDriver`, `Kookaburra::UIDriver` and
597
+ `Kookaburra::APIClient`, `Kookaburra::UIDriver` and
605
598
  `Kookaburra::UIDriver::UIComponent` rely on the Application Driver layer
606
- to interact with your application. In the case of the `APIDriver`,
599
+ to interact with your application. In the case of the `APIClient`,
607
600
  Kookaburra uses the [RestClient] [RestClient] library to send HTTP
608
601
  requests to your application. The `UIDriver` and `UIComponent` rely on
609
602
  whatever is passed to `Kookaburra.new` as the `:browser` option.
data/lib/kookaburra.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  require 'kookaburra/exceptions'
2
2
  require 'kookaburra/mental_model'
3
- require 'kookaburra/given_driver'
3
+ require 'kookaburra/api_driver'
4
4
  require 'kookaburra/ui_driver'
5
5
  require 'kookaburra/configuration'
6
6
 
7
7
  # Kookaburra provides the top-level API that you will access in your test
8
- # implementation, namely the {#given}, {#ui}, and the {#get_data} methods.
8
+ # implementation, namely the {#api}, {#ui}, and the {#get_data} methods.
9
9
  #
10
- # The Kookaburra object ensures that your GivenDriver and UIDriver share the
10
+ # The Kookaburra object ensures that your APIDriver and UIDriver share the
11
11
  # same state with regard to any {Kookaburra::MentalModel} data that is created
12
12
  # during your test run. As such, it is important to ensure that a new instance
13
13
  # of Kookaburra is created for each individual test, otherwise you may wind up
@@ -35,26 +35,26 @@ class Kookaburra
35
35
  end
36
36
 
37
37
  # Returns a new Kookaburra instance that wires together your application's
38
- # GivenDriver and UIDriver with a shared {Kookaburra::MentalModel}.
38
+ # APIDriver and UIDriver with a shared {Kookaburra::MentalModel}.
39
39
  #
40
40
  # @param [Kookaburra::Configuration] configuration (Kookaburra.configuration)
41
41
  def initialize(configuration = Kookaburra.configuration)
42
42
  @configuration = configuration
43
43
  @configuration.mental_model = MentalModel.new
44
- @given_driver_class = configuration.given_driver_class
44
+ @api_driver_class = configuration.api_driver_class
45
45
  @ui_driver_class = configuration.ui_driver_class
46
46
  end
47
47
 
48
- # Returns an instance of your GivenDriver class configured to share test
48
+ # Returns an instance of your APIDriver class configured to share test
49
49
  # fixture data with the UIDriver
50
50
  #
51
- # @return [Kookaburra::GivenDriver]
52
- def given
53
- @given ||= @given_driver_class.new(@configuration)
51
+ # @return [Kookaburra::APIDriver]
52
+ def api
53
+ @api ||= @api_driver_class.new(@configuration)
54
54
  end
55
55
 
56
56
  # Returns an instance of your UIDriver class configured to share test fixture
57
- # data with the GivenDriver and to use the browser driver you specified in
57
+ # data with the APIDriver and to use the browser driver you specified in
58
58
  # {#initialize}
59
59
  #
60
60
  # @return [Kookaburra::UIDriver]
@@ -69,7 +69,7 @@ class Kookaburra
69
69
  # of your application's interface.
70
70
  #
71
71
  # @example
72
- # given.a_widget(:foo)
72
+ # api.create_widget(:foo)
73
73
  # ui.create_a_new_widget(:bar)
74
74
  # ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:foo, :bar)
75
75
  #
@@ -77,10 +77,4 @@ class Kookaburra
77
77
  def get_data(collection_name)
78
78
  @configuration.mental_model.send(collection_name).dup
79
79
  end
80
-
81
- private
82
-
83
- def __mental_model__
84
- @configuration.mental_model
85
- end
86
80
  end
@@ -0,0 +1,214 @@
1
+ require 'restclient'
2
+ require 'core_ext/object/to_query'
3
+ require 'kookaburra/exceptions'
4
+
5
+ class Kookaburra
6
+ # Communicate with a Web Services API
7
+ #
8
+ # You will create a subclass of {APIClient} in your testing
9
+ # implementation to be used with you subclass of
10
+ # {Kookaburra::APIDriver}. While the {GivenDriver} implements the
11
+ # "business domain" DSL for setting up your application state, the
12
+ # {APIClient} maps discreet operations to your application's web
13
+ # service API and can (optionally) handle encoding input data and
14
+ # decoding response bodies to and from your preferred serialization
15
+ # format.
16
+ class APIClient
17
+ class << self
18
+ # Serializes input data
19
+ #
20
+ # If specified, any input data provided to {APIClient#post},
21
+ # {APIClient#put} or {APIClient#request} will be processed through
22
+ # this function prior to being sent to the HTTP server.
23
+ #
24
+ # @yieldparam data [Object] The data parameter that was passed to
25
+ # the request method
26
+ # @yieldreturn [String] The text to be used as the request body
27
+ #
28
+ # @example
29
+ # class MyAPIClient < Kookaburra::APIClient
30
+ # encode_with { |data| JSON.dump(data) }
31
+ # # ...
32
+ # end
33
+ def encode_with(&block)
34
+ define_method(:encode) do |data|
35
+ return if data.nil?
36
+ block.call(data)
37
+ end
38
+ end
39
+
40
+ # Deserialize response body
41
+ #
42
+ # If specified, the response bodies of all requests made using
43
+ # this {APIClient} will be processed through this function prior
44
+ # to being returned.
45
+ #
46
+ # @yieldparam data [String] The response body sent by the HTTP
47
+ # server
48
+ #
49
+ # @yieldreturn [Object] The result of parsing the response body
50
+ # through this function
51
+ #
52
+ # @example
53
+ # class MyAPIClient < Kookaburra::APIClient
54
+ # decode_with { |data| JSON.parse(data) }
55
+ # # ...
56
+ # end
57
+ def decode_with(&block)
58
+ define_method(:decode) do |data|
59
+ block.call(data)
60
+ end
61
+ end
62
+
63
+ # Set custom HTTP headers
64
+ #
65
+ # Can be called multiple times to set HTTP headers that will be
66
+ # provided with every request made by the {APIClient}.
67
+ #
68
+ # @param [String] name The name of the header, e.g. 'Content-Type'
69
+ # @param [String] value The value to which the header is set
70
+ #
71
+ # @example
72
+ # class MyAPIClient < Kookaburra::APIClient
73
+ # header 'Content-Type', 'application/json'
74
+ # header 'Accept', 'application/json'
75
+ # # ...
76
+ # end
77
+ def header(name, value)
78
+ headers[name] = value
79
+ end
80
+
81
+ # Used to retrieve the list of headers within the instance. Not
82
+ # intended to be used elsewhere.
83
+ #
84
+ # @private
85
+ def headers
86
+ @headers ||= {}
87
+ end
88
+ end
89
+
90
+ # Create a new {APIClient} instance
91
+ #
92
+ # @param [Kookaburra::Configuration] configuration
93
+ # @param [RestClient] http_client (optional) Generally only
94
+ # overriden when testing Kookaburra itself
95
+ def initialize(configuration, http_client = RestClient)
96
+ @configuration = configuration
97
+ @http_client = http_client
98
+ end
99
+
100
+ # Convenience method to make a POST request
101
+ #
102
+ # @see APIClient#request
103
+ def post(path, data = nil, headers = {})
104
+ request(:post, path, data, headers)
105
+ end
106
+
107
+ # Convenience method to make a PUT request
108
+ #
109
+ # @see APIClient#request
110
+ def put(path, data = nil, headers = {})
111
+ request(:put, path, data, headers)
112
+ end
113
+
114
+ # Convenience method to make a GET request
115
+ #
116
+ # @see APIClient#request
117
+ def get(path, data = nil, headers = {})
118
+ path = add_querystring_to_path(path, data)
119
+ request(:get, path, nil, headers)
120
+ end
121
+
122
+ # Convenience method to make a DELETE request
123
+ #
124
+ # @see APIClient#request
125
+ def delete(path, data = nil, headers = {})
126
+ path = add_querystring_to_path(path, data)
127
+ request(:delete, path, nil, headers)
128
+ end
129
+
130
+ # Make an HTTP request
131
+ #
132
+ # If you need to make a request other than the typical GET, POST,
133
+ # PUT and DELETE, you can use this method directly.
134
+ #
135
+ # This *will* follow redirects when the server's response code is in
136
+ # the 3XX range. If the response is a 303, the request will be
137
+ # transformed into a GET request.
138
+ #
139
+ # @see APIClient.encode_with
140
+ # @see APIClient.decode_with
141
+ # @see APIClient.header
142
+ # @see APIClient#get
143
+ # @see APIClient#post
144
+ # @see APIClient#put
145
+ # @see APIClient#delete
146
+ #
147
+ # @param [Symbol] method The HTTP verb to use with the request
148
+ # @param [String] path The path to request. Will be joined with the
149
+ # {Kookaburra::Configuration#app_host} setting to build the
150
+ # URL unless a full URL is specified here.
151
+ # @param [Object] data The data to be posted in the request body. If
152
+ # an encoder was specified, this can be any type of object as
153
+ # long as the encoder can serialize it into a String. If no
154
+ # encoder was specified, then this can be one of:
155
+ #
156
+ # * a String - will be passed as is
157
+ # * a Hash - will be encoded as normal HTTP form params
158
+ # * a Hash containing references to one or more Files - will
159
+ # set the content type to multipart/form-data
160
+ #
161
+ # @return [Object] The response body returned by the server. If a
162
+ # decoder was specified, this will return the result of
163
+ # parsing the response body through the decoder function.
164
+ #
165
+ # @raise [Kookaburra::UnexpectedResponse] Raised if the HTTP
166
+ # response received is not in the 2XX-3XX range.
167
+ def request(method, path, data, headers)
168
+ data = encode(data)
169
+ headers = global_headers.merge(headers)
170
+ response = @http_client.send(method, url_for(path), *[data, headers].compact)
171
+ decode(response.body)
172
+ rescue RestClient::Exception => e
173
+ raise_unexpected_response(e)
174
+ end
175
+
176
+ private
177
+
178
+ def add_querystring_to_path(path, data)
179
+ return path if data.nil? || data == {}
180
+ "#{path}?#{data.to_query}"
181
+ end
182
+
183
+ def global_headers
184
+ self.class.headers
185
+ end
186
+
187
+ def url_for(path)
188
+ URI.join(base_url, path).to_s
189
+ end
190
+
191
+ def base_url
192
+ @configuration.app_host
193
+ end
194
+
195
+ def encode(data)
196
+ data
197
+ end
198
+
199
+ def decode(data)
200
+ data
201
+ end
202
+
203
+ def raise_unexpected_response(exception)
204
+ message = <<-END
205
+ Unexpected response from server: #{exception.message}
206
+
207
+ #{exception.http_body}
208
+ END
209
+ new_exception = UnexpectedResponse.new(message)
210
+ new_exception.set_backtrace(exception.backtrace)
211
+ raise new_exception
212
+ end
213
+ end
214
+ end