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 +4 -4
- data/.travis.yml +1 -1
- data/README.markdown +52 -59
- data/lib/kookaburra.rb +11 -17
- data/lib/kookaburra/api_client.rb +214 -0
- data/lib/kookaburra/api_driver.rb +47 -196
- data/lib/kookaburra/configuration.rb +4 -4
- data/lib/kookaburra/mental_model.rb +16 -4
- data/lib/kookaburra/test_helpers.rb +15 -37
- data/lib/kookaburra/version.rb +1 -1
- data/spec/integration/test_a_rack_application_spec.rb +34 -356
- data/spec/kookaburra/{api_driver_spec.rb → api_client_spec.rb} +14 -14
- data/spec/kookaburra/configuration_spec.rb +6 -6
- data/spec/kookaburra/mental_model_spec.rb +13 -1
- data/spec/kookaburra/test_helpers_spec.rb +5 -44
- data/spec/kookaburra_spec.rb +7 -7
- data/spec/support/json_api_app_and_kookaburra_drivers.rb +372 -0
- metadata +41 -42
- data/lib/kookaburra/given_driver.rb +0 -65
- data/lib/kookaburra/mental_model_matcher.rb +0 -138
- data/spec/kookaburra/mental_model_matcher_spec.rb +0 -237
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0096d882dd0e4a30ea2ff6f38744a307ca77b596
|
4
|
+
data.tar.gz: 97dd32061c90235865ed398517c59c356d0b3b38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2022e2a53bef18d607ada46e5043d2f17c79f4699f4ee02b5d06c4b12c9d219727fee3bb2cee84e55383a35697d2918552a295b49e165d13bc0505a311c59097
|
7
|
+
data.tar.gz: 86883520f7835ebcd750f01448d39727ffa48c9f8c8d564b5b8ca42bd12e17468d6f7ee11aeb958a398f1facf939a09bb108f2c3e7bc6e9e68f67c246664d047
|
data/.travis.yml
CHANGED
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
|
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
|
57
|
-
the default setup only starts the server up on-demand when you
|
58
|
-
that requires the browser to interact with the web
|
59
|
-
|
60
|
-
process on your own. Otherwise the server
|
61
|
-
|
62
|
-
|
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
|
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
|
86
|
+
# Change these to the files that define your custom APIDriver and UIDriver
|
87
87
|
# implementations.
|
88
|
-
require 'my_app/kookaburra/
|
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.
|
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, #
|
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
|
120
|
+
# Change these to the files that define your custom APIDriver and UIDriver
|
121
121
|
# implementations.
|
122
|
-
require 'my_app/kookaburra/
|
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.
|
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
|
171
|
+
# Change these to the files that define your custom APIDriver and UIDriver
|
172
172
|
# implementations.
|
173
|
-
require 'my_app/kookaburra/
|
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.
|
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
|
201
|
+
# Change these to the files that define your custom APIDriver and UIDriver
|
202
202
|
# implementations.
|
203
|
-
require 'my_app/kookaburra/
|
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.
|
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::
|
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::
|
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
|
-
|
318
|
+
api.existing_account
|
319
319
|
end
|
320
320
|
|
321
321
|
Given "I have previously specified default payment options" do
|
322
|
-
|
322
|
+
api.default_payment_options_specified
|
323
323
|
end
|
324
324
|
|
325
325
|
Given "I have previously specified default shipping options" do
|
326
|
-
|
326
|
+
api.default_shipping_options_specified
|
327
327
|
end
|
328
328
|
|
329
329
|
Given "I have an item in my shopping cart" do
|
330
|
-
|
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
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
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 `
|
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 `
|
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 `
|
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 `
|
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
|
-
####
|
462
|
+
#### API Driver ####
|
463
463
|
|
464
|
-
The `Kookaburra::
|
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::
|
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/
|
470
|
+
# lib/my_app/kookaburra/api_driver.rb
|
471
471
|
|
472
|
-
class MyApp::Kookaburra::
|
473
|
-
# Specify the
|
472
|
+
class MyApp::Kookaburra::APIDriver < Kookaburra::APIDriver
|
473
|
+
# Specify the APIClient to use
|
474
474
|
def api
|
475
|
-
@api ||= MyApp::Kookaburra::
|
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
|
-
|
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
|
-
|
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::
|
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::
|
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
|
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 `
|
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::
|
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 `
|
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/
|
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 {#
|
8
|
+
# implementation, namely the {#api}, {#ui}, and the {#get_data} methods.
|
9
9
|
#
|
10
|
-
# The Kookaburra object ensures that your
|
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
|
-
#
|
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
|
-
@
|
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
|
48
|
+
# Returns an instance of your APIDriver class configured to share test
|
49
49
|
# fixture data with the UIDriver
|
50
50
|
#
|
51
|
-
# @return [Kookaburra::
|
52
|
-
def
|
53
|
-
@
|
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
|
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
|
-
#
|
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
|