kookaburra 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,13 @@
1
- require 'kookaburra/api_driver'
1
+ require 'kookaburra/api_client'
2
2
 
3
- describe Kookaburra::APIDriver do
3
+ describe Kookaburra::APIClient do
4
4
  def url_for(uri)
5
5
  URI.join('http://example.com', uri).to_s
6
6
  end
7
7
 
8
8
  let(:configuration) { double('Configuration', :app_host => 'http://example.com') }
9
9
 
10
- let(:api) { Kookaburra::APIDriver.new(configuration, client) }
10
+ let(:api) { Kookaburra::APIClient.new(configuration, client) }
11
11
 
12
12
  let(:response) { double('RestClient::Response', body: 'foo', code: 200) }
13
13
 
@@ -20,20 +20,20 @@ describe Kookaburra::APIDriver do
20
20
  end
21
21
 
22
22
  it 'returns the response body' do
23
- api.send(http_verb, '/foo').should == 'foo'
23
+ expect(api.send(http_verb, '/foo')).to eq 'foo'
24
24
  end
25
25
 
26
26
  it 'raises an UnexpectedResponse if the request is not successful' do
27
27
  response.stub(code: 500)
28
28
  client.stub(http_verb).and_raise(RestClient::Exception.new(response))
29
- lambda { api.send(http_verb, '/foo') } \
30
- .should raise_error(Kookaburra::UnexpectedResponse)
29
+ expect{ api.send(http_verb, '/foo') } \
30
+ .to raise_error(Kookaburra::UnexpectedResponse)
31
31
  end
32
32
 
33
33
  let(:expect_client_to_receive_headers) { ->(expected_headers) {
34
34
  # Some HTTP verb methods pass data, some don't, and their arity
35
35
  # is different
36
- client.should_receive(http_verb) do |path, data_or_headers, headers|
36
+ allow(client).to receive(http_verb) do |path, data_or_headers, headers|
37
37
  headers ||= data_or_headers
38
38
  expect(headers).to eq(expected_headers)
39
39
  response
@@ -42,7 +42,7 @@ describe Kookaburra::APIDriver do
42
42
 
43
43
  context 'when custom global headers are specified' do
44
44
  let(:api) {
45
- klass = Class.new(Kookaburra::APIDriver) do
45
+ klass = Class.new(Kookaburra::APIClient) do
46
46
  header 'Header-Foo', 'Baz'
47
47
  header 'Header-Bar', 'Bam'
48
48
  end
@@ -100,14 +100,14 @@ describe Kookaburra::APIDriver do
100
100
 
101
101
  context 'when a custom decoder is specified' do
102
102
  let(:api) {
103
- klass = Class.new(Kookaburra::APIDriver) do
103
+ klass = Class.new(Kookaburra::APIClient) do
104
104
  decode_with { |data| :some_decoded_data }
105
105
  end
106
106
  klass.new(configuration, client)
107
107
  }
108
108
 
109
109
  it "decodes response bodies from requests" do
110
- api.send(http_verb, '/foo').should == :some_decoded_data
110
+ expect(api.send(http_verb, '/foo')).to eq :some_decoded_data
111
111
  end
112
112
  end
113
113
  end
@@ -121,9 +121,9 @@ describe Kookaburra::APIDriver do
121
121
 
122
122
  context 'when a custom encoder is specified' do
123
123
  let(:api) {
124
- klass = Class.new(Kookaburra::APIDriver) do
124
+ klass = Class.new(Kookaburra::APIClient) do
125
125
  encode_with { |data|
126
- data.should == :some_ruby_data
126
+ raise "Wrong data!" unless data == :some_ruby_data
127
127
  :some_encoded_data
128
128
  }
129
129
  end
@@ -131,7 +131,7 @@ describe Kookaburra::APIDriver do
131
131
  }
132
132
 
133
133
  it "encodes input to requests" do
134
- client.should_receive(http_verb) do |_, data, _|
134
+ expect(client).to receive(http_verb) do |_, data, _|
135
135
  data.should == :some_encoded_data
136
136
  response
137
137
  end
@@ -145,7 +145,7 @@ describe Kookaburra::APIDriver do
145
145
  shared_examples_for 'it encodes data as a querystring' do |http_verb|
146
146
  context "(#{http_verb})" do
147
147
  it 'adds data as querystirng params' do
148
- client.should_receive(http_verb).with(url_for('/foo?bar=baz&yak=shaved'), {}) \
148
+ expect(client).to receive(http_verb).with(url_for('/foo?bar=baz&yak=shaved'), {}) \
149
149
  .and_return(response)
150
150
  api.send(http_verb, '/foo', bar: 'baz', yak: 'shaved')
151
151
  end
@@ -2,7 +2,7 @@ require 'kookaburra/configuration'
2
2
  require 'support/shared_examples/it_has_a_dependency_accessor'
3
3
 
4
4
  describe Kookaburra::Configuration do
5
- it_behaves_like :it_has_a_dependency_accessor, :given_driver_class
5
+ it_behaves_like :it_has_a_dependency_accessor, :api_driver_class
6
6
  it_behaves_like :it_has_a_dependency_accessor, :ui_driver_class
7
7
  it_behaves_like :it_has_a_dependency_accessor, :browser
8
8
  it_behaves_like :it_has_a_dependency_accessor, :app_host
@@ -13,13 +13,13 @@ describe Kookaburra::Configuration do
13
13
  it 'returns the block that it was last given' do
14
14
  block = lambda { 'foo' }
15
15
  subject.server_error_detection(&block)
16
- subject.server_error_detection.should == block
16
+ expect(subject.server_error_detection).to eq block
17
17
  end
18
18
  end
19
19
 
20
20
  describe '#app_host_uri' do
21
21
  it 'returns a URI version of the #app_host attribute via URI.parse' do
22
- URI.should_receive(:parse) \
22
+ expect(URI).to receive(:parse) \
23
23
  .with('http://example.com') \
24
24
  .and_return(:a_parsed_uri)
25
25
  subject.app_host = 'http://example.com'
@@ -27,13 +27,13 @@ describe Kookaburra::Configuration do
27
27
  end
28
28
 
29
29
  it 'changes if #app_host changes' do
30
- URI.stub(:parse) do |url|
30
+ allow(URI).to receive(:parse) do |url|
31
31
  url.to_sym
32
32
  end
33
33
  subject.app_host = 'http://example.com'
34
- subject.app_host_uri.should == 'http://example.com'.to_sym
34
+ expect(subject.app_host_uri).to eq 'http://example.com'.to_sym
35
35
  subject.app_host = 'http://foo.example.com'
36
- subject.app_host_uri.should == 'http://foo.example.com'.to_sym
36
+ expect(subject.app_host_uri).to eq 'http://foo.example.com'.to_sym
37
37
  end
38
38
  end
39
39
  end
@@ -86,7 +86,8 @@ describe Kookaburra::MentalModel do
86
86
  describe '#deleted' do
87
87
  it 'generates a new subcollection if none exists' do
88
88
  initialized_collection = collection
89
- Kookaburra::MentalModel::Collection.should_receive(:new).with("deleted")
89
+ Kookaburra::MentalModel::Collection.should_receive(:new) \
90
+ .with("#{initialized_collection.name}.deleted")
90
91
  initialized_collection.deleted
91
92
  end
92
93
 
@@ -121,6 +122,17 @@ describe Kookaburra::MentalModel do
121
122
  new_collection[:foo][:bar].should == 'baz'
122
123
  new_collection[:foo][:bar].__id__.should_not === collection[:foo][:bar].__id__
123
124
  end
125
+
126
+ context 'when there are deleted items present' do
127
+ it 'also dupes the deleted items' do
128
+ collection[:foo] = 'foo'
129
+ collection[:bar] = 'bar'
130
+ deleted = collection.delete(:bar)
131
+ new_collection = collection.dup
132
+ expect(new_collection.deleted[:bar]).to eq deleted
133
+ expect(new_collection.deleted[:bar]).to_not equal deleted
134
+ end
135
+ end
124
136
  end
125
137
  end
126
138
  end
@@ -5,14 +5,14 @@ describe Kookaburra::TestHelpers do
5
5
 
6
6
  before(:all) do
7
7
  Kookaburra.configure do |c|
8
- c.given_driver_class = Kookaburra::GivenDriver
8
+ c.api_driver_class = Kookaburra::APIDriver
9
9
  c.ui_driver_class = Kookaburra::UIDriver
10
10
  end
11
11
  end
12
12
 
13
13
  after(:all) do
14
14
  Kookaburra.configure do |c|
15
- c.given_driver_class = nil
15
+ c.api_driver_class = nil
16
16
  c.ui_driver_class = nil
17
17
  end
18
18
  end
@@ -29,9 +29,9 @@ describe Kookaburra::TestHelpers do
29
29
  end
30
30
 
31
31
  describe "methods delegated to #k" do
32
- it "includes #given" do
33
- k.should_receive(:given)
34
- given
32
+ it "includes #api" do
33
+ k.should_receive(:api)
34
+ api
35
35
  end
36
36
 
37
37
  it "includes #ui" do
@@ -39,43 +39,4 @@ describe Kookaburra::TestHelpers do
39
39
  ui
40
40
  end
41
41
  end
42
-
43
- describe "methods related to the mental model" do
44
- before(:each) do
45
- mm = k.send(:__mental_model__)
46
- mm.widgets[:foo] = 'FOO'
47
- end
48
-
49
- describe "#match_mental_model_of" do
50
- it "does a positive match" do
51
- ['FOO'].should match_mental_model_of(:widgets)
52
- end
53
-
54
- it "does a negative match" do
55
- ['BAR'].should_not match_mental_model_of(:widgets)
56
- end
57
- end
58
-
59
- describe "#assert_mental_model_matches" do
60
- it "does a positive assertion" do
61
- actual = ['FOO']
62
- actual.should match_mental_model_of(:widgets) # Sanity check
63
- self.should_receive(:assert).never
64
- self.assert_mental_model_matches(:widgets, actual)
65
- end
66
-
67
- it "does a negative assertion" do
68
- actual = ['BAR']
69
- self.should_receive(:assert).with(false, kind_of(String))
70
- self.assert_mental_model_matches(:widgets, actual)
71
- end
72
-
73
- it "does a negative assertion with a custom message" do
74
- actual = ['YAK']
75
- psa = 'Put the razor down and step away!'
76
- self.should_receive(:assert).with(false, psa)
77
- self.assert_mental_model_matches(:widgets, actual, psa)
78
- end
79
- end
80
- end
81
42
  end
@@ -7,14 +7,14 @@ describe Kookaburra do
7
7
 
8
8
  let(:k) { Kookaburra.new(configuration) }
9
9
 
10
- describe '#given' do
11
- it 'returns an instance of the configured GivenDriver' do
12
- my_given_driver_class = double(Class)
13
- my_given_driver_class.should_receive(:new) \
10
+ describe '#api' do
11
+ it 'returns an instance of the configured APIDriver' do
12
+ my_api_driver_class = double(Class)
13
+ my_api_driver_class.should_receive(:new) \
14
14
  .with(configuration) \
15
- .and_return(:a_given_driver)
16
- configuration.stub(:given_driver_class => my_given_driver_class)
17
- k.given.should == :a_given_driver
15
+ .and_return(:an_api_driver)
16
+ configuration.stub(:api_driver_class => my_api_driver_class)
17
+ k.api.should == :an_api_driver
18
18
  end
19
19
  end
20
20
 
@@ -0,0 +1,372 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+
4
+ # This is the fixture Rack application against which the integration
5
+ # test will run. It uses class variables to persist data, because
6
+ # Sinatra will instantiate a new instance of TestRackApp for each
7
+ # request.
8
+ class JsonApiApp < Sinatra::Base
9
+ enable :sessions
10
+ disable :show_exceptions
11
+
12
+ def parse_json_req_body
13
+ request.body.rewind
14
+ JSON.parse(request.body.read, :symbolize_names => true)
15
+ end
16
+
17
+ post '/users' do
18
+ user_data = parse_json_req_body
19
+ @@users ||= {}
20
+ @@users[user_data[:email]] = user_data
21
+ status 201
22
+ headers 'Content-Type' => 'application/json'
23
+ body user_data.to_json
24
+ end
25
+
26
+ post '/session' do
27
+ user = @@users[params[:email]]
28
+ if user && user[:password] == params[:password]
29
+ session[:logged_in] = true
30
+ status 200
31
+ body 'You are logged in!'
32
+ else
33
+ session[:logged_in] = false
34
+ status 403
35
+ body 'Log in failed!'
36
+ end
37
+ end
38
+
39
+ get '/session/new' do
40
+ body <<-EOF
41
+ <html>
42
+ <head>
43
+ <title>Sign In</title>
44
+ </head>
45
+ <body>
46
+ <div id="sign_in_screen">
47
+ <form action="/session" method="POST">
48
+ <label for="email">Email:</label>
49
+ <input id="email" name="email" type="text" />
50
+
51
+ <label for="password">Password:</label>
52
+ <input id="password" name="password" type="password" />
53
+
54
+ <input type="submit" value="Sign In" />
55
+ </form>
56
+ </div>
57
+ </body>
58
+ </html>
59
+ EOF
60
+ end
61
+
62
+ def delete_widget(id)
63
+ @@widgets.delete_if do |w|
64
+ w[:id] == id
65
+ end
66
+ end
67
+
68
+ post '/widgets/:widget_id' do
69
+ delete_widget(params['widget_id'])
70
+ redirect to('/widgets')
71
+ end
72
+
73
+ delete '/widgets/:widget_id' do
74
+ result = delete_widget(params['widget_id'])
75
+ status 200
76
+ body result.to_json
77
+ end
78
+
79
+ get '/widgets/new' do
80
+ body <<-EOF
81
+ <html>
82
+ <head>
83
+ <title>New Widget</title>
84
+ </head>
85
+ <body>
86
+ <div id="widget_form">
87
+ <form action="/widgets" method="POST">
88
+ <label for="name">Name:</label>
89
+ <input id="name" name="name" type="text" />
90
+
91
+ <input type="submit" value="Save" />
92
+ </form>
93
+ </div>
94
+ </body>
95
+ </html>
96
+ EOF
97
+ end
98
+
99
+ post '/widgets' do
100
+ @@widgets ||= []
101
+ widget_data = if request.media_type == 'application/json'
102
+ parse_json_req_body
103
+ else
104
+ {:name => params['name']}
105
+ end
106
+ widget_data[:id] = UUID.new.generate
107
+ @@widgets << widget_data
108
+ @@last_widget_created = widget_data
109
+ request.accept.each do |type|
110
+ case type.to_s
111
+ when 'application/json'
112
+ status 201
113
+ headers 'Content-Type' => 'application/json'
114
+ halt widget_data.to_json
115
+ else
116
+ halt redirect to('/widgets')
117
+ end
118
+ end
119
+ end
120
+
121
+ get '/widgets' do
122
+ @@widgets ||= []
123
+ if request.media_type == 'application/json'
124
+ status 200
125
+ headers 'Content-Type' => 'application/json'
126
+ halt @@widgets.to_json
127
+ else
128
+ raise "Not logged in!" unless session[:logged_in]
129
+ last_widget_created, @@last_widget_created = @@last_widget_created, nil
130
+ content = ''
131
+ content << <<-EOF
132
+ <html>
133
+ <head>
134
+ <title>Widgets</title>
135
+ </head>
136
+ <body>
137
+ <div id="widget_list">
138
+ EOF
139
+ if last_widget_created
140
+ content << <<-EOF
141
+ <div class="last_widget created">
142
+ <span class="id">#{last_widget_created[:id]}</span>
143
+ <span class="name">#{last_widget_created[:name]}</span>
144
+ </div>
145
+ EOF
146
+ end
147
+ content << <<-EOF
148
+ <ul>
149
+ EOF
150
+ @@widgets.each do |w|
151
+ content << <<-EOF
152
+ <li class="widget_summary">
153
+ <span class="id">#{w[:id]}</span>
154
+ <span class="name">#{w[:name]}</span>
155
+ <form id="delete_#{w[:id]}" action="/widgets/#{w[:id]}" method="POST">
156
+ <button type="submit" value="Delete" />
157
+ </form>
158
+ </li>
159
+ EOF
160
+ end
161
+ content << <<-EOF
162
+ </ul>
163
+ <a href="/widgets/new">New Widget</a>
164
+ </div>
165
+ </body>
166
+ </html>
167
+ EOF
168
+ body content
169
+ end
170
+ end
171
+
172
+ get '/error_page' do
173
+ content = <<-EOF
174
+ <html>
175
+ <head>
176
+ <title>Internal Server Error</title>
177
+ </head>
178
+ <body>
179
+ <p>A Purposeful Error</p>
180
+ </body>
181
+ </html>
182
+ EOF
183
+ body content
184
+ end
185
+
186
+ error do
187
+ e = request.env['sinatra.error']
188
+ body << <<-EOF
189
+ <html>
190
+ <head>
191
+ <title>Internal Server Error</title>
192
+ </head>
193
+ <body>
194
+ <pre>
195
+ #{e.to_s}\n#{e.backtrace.join("\n")}
196
+ </pre>
197
+ </body>
198
+ </html>
199
+ EOF
200
+ end
201
+ end
202
+
203
+ class MyAPIClient < Kookaburra::APIClient
204
+ encode_with { |data| JSON.dump(data) }
205
+ decode_with { |data| JSON.parse(data) }
206
+ header 'Content-Type', 'application/json'
207
+ header 'Accept', 'application/json'
208
+
209
+ def create_user(user_data)
210
+ post '/users', user_data
211
+ end
212
+
213
+ def create_widget(widget_data)
214
+ post '/widgets', widget_data
215
+ end
216
+
217
+ def delete_widget(id)
218
+ delete "/widgets/#{id}"
219
+ end
220
+
221
+ def widgets
222
+ get '/widgets'
223
+ end
224
+ end
225
+
226
+ class MyAPIDriver < Kookaburra::APIDriver
227
+ def api
228
+ MyAPIClient.new(configuration)
229
+ end
230
+
231
+ def create_user(name)
232
+ mental_model.users.fetch(name) { |name|
233
+ user = {'email' => 'bob@example.com', 'password' => '12345'}
234
+ result = api.create_user(user)
235
+ mental_model.users[name] = result
236
+ }
237
+ end
238
+
239
+ def create_widget(name, attributes = {})
240
+ mental_model.widgets.fetch(name) { |name|
241
+ widget = {'name' => name}.merge(attributes)
242
+ result = api.create_widget(widget)
243
+ mental_model.widgets[name] = result
244
+ }
245
+ end
246
+
247
+ def delete_widget(name)
248
+ widget = mental_model.widgets.delete(name)
249
+ api.delete_widget(widget['id'])
250
+ end
251
+
252
+ def widgets
253
+ api.widgets
254
+ end
255
+ end
256
+
257
+ class SignInScreen < Kookaburra::UIDriver::UIComponent
258
+ def component_path
259
+ '/session/new'
260
+ end
261
+
262
+ # Use default component locator value
263
+ #
264
+ # def component_locator
265
+ # '#sign_in_screen'
266
+ # end
267
+
268
+ def sign_in(user_data)
269
+ fill_in 'Email:', :with => user_data['email']
270
+ fill_in 'Password:', :with => user_data['password']
271
+ click_button 'Sign In'
272
+ end
273
+ end
274
+
275
+ class ErrorPage < Kookaburra::UIDriver::UIComponent
276
+ def component_path
277
+ '/error_page'
278
+ end
279
+ end
280
+
281
+ class WidgetDataContainer
282
+ def initialize(element)
283
+ @element = element
284
+ end
285
+
286
+ def to_hash
287
+ {
288
+ 'id' => @element.find('.id').text,
289
+ 'name' => @element.find('.name').text
290
+ }
291
+ end
292
+ end
293
+
294
+ class LastWidgetCreated < Kookaburra::UIDriver::UIComponent
295
+ def component_locator
296
+ @options[:component_locator]
297
+ end
298
+
299
+ def data
300
+ raise "Foo" unless visible?
301
+ WidgetDataContainer.new(self).to_hash
302
+ end
303
+ end
304
+
305
+ class WidgetList < Kookaburra::UIDriver::UIComponent
306
+ ui_component :last_widget_created, LastWidgetCreated, :component_locator => '#widget_list .last_widget.created'
307
+
308
+ def component_path
309
+ '/widgets'
310
+ end
311
+
312
+ def component_locator
313
+ '#widget_list'
314
+ end
315
+
316
+ def widgets
317
+ all('.widget_summary').map do |el|
318
+ WidgetDataContainer.new(el).to_hash
319
+ end
320
+ end
321
+
322
+ def choose_to_create_new_widget
323
+ click_on 'New Widget'
324
+ end
325
+
326
+ def choose_to_delete_widget(widget_data)
327
+ find("#delete_#{widget_data['id']}").click_button('Delete')
328
+ end
329
+ end
330
+
331
+ class WidgetForm < Kookaburra::UIDriver::UIComponent
332
+ def component_locator
333
+ '#widget_form'
334
+ end
335
+
336
+ def submit(widget_data)
337
+ fill_in 'Name:', :with => widget_data['name']
338
+ click_on 'Save'
339
+ end
340
+ end
341
+
342
+ class MyUIDriver < Kookaburra::UIDriver
343
+ ui_component :error_page, ErrorPage
344
+ ui_component :sign_in_screen, SignInScreen
345
+ ui_component :widget_list, WidgetList
346
+ ui_component :widget_form, WidgetForm
347
+
348
+ def sign_in(name)
349
+ address_bar.go_to sign_in_screen
350
+ sign_in_screen.sign_in(mental_model.users[name])
351
+ end
352
+
353
+ def error_on_purpose
354
+ address_bar.go_to error_page
355
+ end
356
+
357
+ def view_widget_list
358
+ address_bar.go_to widget_list
359
+ end
360
+
361
+ def create_widget(name, attributes = {})
362
+ assert widget_list.visible?, "Widget list is not visible!"
363
+ widget_list.choose_to_create_new_widget
364
+ widget_form.submit('name' => 'My Widget')
365
+ mental_model.widgets[name] = widget_list.last_widget_created.data
366
+ end
367
+
368
+ def delete_widget(name)
369
+ assert widget_list.visible?, "Widget list is not visible!"
370
+ widget_list.choose_to_delete_widget(mental_model.widgets.delete(name))
371
+ end
372
+ end