rspec-cramp 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README.md +90 -0
- data/lib/rspec/cramp/extensions/cramp/action.rb +17 -0
- data/lib/rspec/cramp/matchers/respond_with.rb +16 -0
- data/lib/rspec/cramp/mock_response.rb +135 -0
- data/lib/rspec/cramp/shared_context.rb +126 -0
- data/lib/rspec/cramp.rb +6 -0
- data/spec/examples/basic_spec.rb +31 -0
- data/spec/examples/errors_spec.rb +26 -0
- data/spec/examples/headers_spec.rb +25 -0
- data/spec/examples/low_level_spec.rb +26 -0
- data/spec/examples/sample_actions.rb +42 -0
- data/spec/examples/tests.log +0 -0
- data/spec/rspec_cramp_spec.rb +442 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/tests.log +1950 -0
- metadata +115 -0
@@ -0,0 +1,442 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
module Cramp
|
4
|
+
|
5
|
+
describe "rspec-cramp" do
|
6
|
+
class SuccessfulResponse < Cramp::Action
|
7
|
+
on_start :render_and_finish
|
8
|
+
def render_and_finish
|
9
|
+
render "ok"
|
10
|
+
finish
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class HelloWorldResponse < Cramp::Action
|
15
|
+
on_start :render_and_finish
|
16
|
+
def render_and_finish
|
17
|
+
render "Hello, world"
|
18
|
+
finish
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MultipartResponse < Cramp::Action
|
23
|
+
on_start :render_and_finish
|
24
|
+
def render_and_finish
|
25
|
+
render "part1"
|
26
|
+
render "part2"
|
27
|
+
finish
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ErrorResponse < Cramp::Action
|
32
|
+
on_start :just_finish
|
33
|
+
def respond_with
|
34
|
+
[500, {'Content-Type' => 'text/html'}]
|
35
|
+
end
|
36
|
+
def just_finish
|
37
|
+
finish
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
class RaiseBeforeStart < Cramp::Action
|
43
|
+
before_start :raise_error
|
44
|
+
def raise_error
|
45
|
+
raise "Error in before_start"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class RaiseOnStart < Cramp::Action
|
50
|
+
on_start :raise_error
|
51
|
+
def raise_error
|
52
|
+
raise "Error in on_start"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class RaiseOnFinish < Cramp::Action
|
57
|
+
on_start :just_finish
|
58
|
+
on_finish :raise_error
|
59
|
+
def just_finish
|
60
|
+
finish
|
61
|
+
end
|
62
|
+
def raise_error
|
63
|
+
raise "Error in on_finish"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class NoResponse < Cramp::Action
|
68
|
+
on_start :noop
|
69
|
+
def noop
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class CustomHeaders < Cramp::Action
|
74
|
+
on_start :ok_and_finish
|
75
|
+
def respond_with
|
76
|
+
[200, {'Extra-Header' => 'ABCD', 'Another-One' => 'QWERTY'}]
|
77
|
+
end
|
78
|
+
def ok_and_finish
|
79
|
+
render "ok"
|
80
|
+
finish
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class SseAction < Cramp::Action
|
85
|
+
self.transport = :sse
|
86
|
+
periodic_timer :write_hello, :every => 0.5
|
87
|
+
def write_hello
|
88
|
+
@num ||= 1
|
89
|
+
render "Hello #{@num}"
|
90
|
+
@num += 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class RequestHeaders < Cramp::Action
|
95
|
+
on_start :render_request_headers
|
96
|
+
def render_request_headers
|
97
|
+
response = http_headers.inject("Request headers:\n") {|acc, (k,v)| acc << "#{k}: #{v}\n"; acc}
|
98
|
+
render response
|
99
|
+
finish
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def http_headers
|
105
|
+
@env.inject({}){|acc, (k,v)| acc[$1.upcase] = v if k =~ /^http_(.*)/i; acc}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class RequestParams < Cramp::Action
|
110
|
+
on_start :render_and_finish
|
111
|
+
def render_and_finish
|
112
|
+
render params[:text]
|
113
|
+
finish
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def routes
|
119
|
+
HttpRouter.new do
|
120
|
+
add('/200').to SuccessfulResponse
|
121
|
+
add('/hello_world').to HelloWorldResponse
|
122
|
+
add('/multipart').to MultipartResponse
|
123
|
+
add('/500').to ErrorResponse
|
124
|
+
add('/no_response').to NoResponse
|
125
|
+
add('/custom_header').to CustomHeaders
|
126
|
+
add('/raise_before_start').to RaiseBeforeStart
|
127
|
+
add('/raise_on_start').to RaiseOnStart
|
128
|
+
add('/raise_on_finish').to RaiseOnFinish
|
129
|
+
add('/sse').to SseAction
|
130
|
+
add('/get_only').request_method('GET').to SuccessfulResponse
|
131
|
+
add('/post_only').request_method('POST').to SuccessfulResponse
|
132
|
+
add('/put_only').request_method('PUT').to SuccessfulResponse
|
133
|
+
add('/delete_only').request_method('DELETE').to SuccessfulResponse
|
134
|
+
add('/request_headers').to RequestHeaders
|
135
|
+
add('/request_params').to RequestParams
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "'respond_with' matcher", :cramp => true do
|
140
|
+
def app
|
141
|
+
routes
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should raise error for unsupported match options" do
|
145
|
+
lambda { get("/200").should respond_with :whatever => "ABC" }.should raise_error
|
146
|
+
end
|
147
|
+
|
148
|
+
shared_examples_for "async_request" do |method|
|
149
|
+
|
150
|
+
describe "timeout" do
|
151
|
+
it "- timeout when no response" do
|
152
|
+
lambda { send(method, "/no_response") }.should raise_error Timeout::Error
|
153
|
+
end
|
154
|
+
it "- allow the timeout to be defined by the user" do
|
155
|
+
lambda do
|
156
|
+
timeout(2) do
|
157
|
+
lambda {send(method, "/no_response", {:timeout => 1}) }.should raise_error Timeout::Error
|
158
|
+
end
|
159
|
+
end.should_not raise_error Timeout::Error
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# TODO Rewrite the repetitive code below using data-based spec generation.
|
164
|
+
describe "exact match on response status" do
|
165
|
+
it "should match successful response" do
|
166
|
+
send(method, "/200").should respond_with :status => 200
|
167
|
+
send(method, "/200").should respond_with :status => "200"
|
168
|
+
send(method, "/200").should respond_with :status => :ok
|
169
|
+
end
|
170
|
+
it "should match error response" do
|
171
|
+
send(method, "/500").should respond_with :status => 500
|
172
|
+
send(method, "/500").should respond_with :status => "500"
|
173
|
+
send(method, "/500").should respond_with :status => :error
|
174
|
+
send(method, "/500").should_not respond_with :status => 200
|
175
|
+
send(method, "/500").should_not respond_with :status => "200"
|
176
|
+
send(method, "/500").should_not respond_with :status => :ok
|
177
|
+
end
|
178
|
+
it "should match non-async errors from http router" do
|
179
|
+
send(method, "/404").should respond_with :status => 404
|
180
|
+
send(method, "/404").should respond_with :status => "404"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "regex match on response status" do
|
185
|
+
it "should match successful response" do
|
186
|
+
send(method, "/200").should respond_with :status => /^2.*/
|
187
|
+
end
|
188
|
+
it "should match error response" do
|
189
|
+
send(method, "/500").should respond_with :status => /^5.*/
|
190
|
+
send(method, "/500").should_not respond_with :status => /^2.*/
|
191
|
+
end
|
192
|
+
it "should match non-sync errors from http router" do
|
193
|
+
send(method, "/404").should respond_with :status => /^4.*/
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe "exact match on response header values" do
|
198
|
+
it "should match with one expected header" do
|
199
|
+
send(method, "/custom_header").should respond_with :headers => {"Extra-Header" => "ABCD"}
|
200
|
+
end
|
201
|
+
it "should match all with two expected headers" do
|
202
|
+
send(method, "/custom_header").should respond_with :headers => {"Extra-Header" => "ABCD", "Another-One" => "QWERTY"}
|
203
|
+
end
|
204
|
+
it "should not match if value does not match" do
|
205
|
+
send(method, "/custom_header").should_not respond_with :headers => {"Extra-Header" => "1234"}
|
206
|
+
end
|
207
|
+
it "should not match iff the header isn't there" do
|
208
|
+
send(method, "/custom_header").should_not respond_with :headers => {"Non-Existent-One" => "QWERTY"}
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "regex match on response header values" do
|
213
|
+
it "should match with one expected header" do
|
214
|
+
send(method, "/custom_header").should respond_with :headers => {"Extra-Header" => /^ABCD$/}
|
215
|
+
end
|
216
|
+
it "should match all with two expected headers" do
|
217
|
+
send(method, "/custom_header").should respond_with :headers => {"Extra-Header" => /^ABCD$/, "Another-One" => /^QWERTY$/}
|
218
|
+
end
|
219
|
+
it "should not match if value does not match" do
|
220
|
+
send(method, "/custom_header").should_not respond_with :headers => {"Extra-Header" => /^1234$/}
|
221
|
+
end
|
222
|
+
it "should not match iff the header isn't there" do
|
223
|
+
send(method, "/custom_header").should_not respond_with :headers => {"Non-Existent-One" => /^QWERTY$/}
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe "regex match on response header fields" do
|
228
|
+
it "should match with one expected header" do
|
229
|
+
send(method, "/custom_header").should respond_with :headers => {/Extra\-Header/i => /^ABCD$/}
|
230
|
+
end
|
231
|
+
it "should match all with two expected headers" do
|
232
|
+
send(method, "/custom_header").should respond_with :headers => {/Extra\-Header/i => /^ABCD$/, "Another-One" => /^QWERTY$/}
|
233
|
+
end
|
234
|
+
it "should not match if value does not match" do
|
235
|
+
send(method, "/custom_header").should_not respond_with :headers => {/Extra\-Header/i => /^1234$/}
|
236
|
+
end
|
237
|
+
it "should not match iff the header isn't there" do
|
238
|
+
send(method, "/custom_header").should_not respond_with :headers => {/Non\-Existent\-One/i => /^QWERTY$/}
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# FIXME How to handle a situation where nothing is rendered? get reads the body...
|
243
|
+
|
244
|
+
describe "exact match on response body" do
|
245
|
+
it "should match with successful response" do
|
246
|
+
send(method, "/200").should respond_with :body => "ok"
|
247
|
+
send(method, "/200").should_not respond_with :body => "wrong"
|
248
|
+
end
|
249
|
+
it "should not load body on error response" do
|
250
|
+
# TODO Not sure about this behaviour. What do you think? Use "Something went wrong"?
|
251
|
+
send(method, "/500").should respond_with :body => /.*Cramp::Body.*/
|
252
|
+
end
|
253
|
+
it "should match non-async response from http router" do
|
254
|
+
send(method, "/404").should respond_with :body => "Something went wrong"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
describe "regex match on response body" do
|
258
|
+
it "should match the body" do
|
259
|
+
send(method, "/hello_world").should respond_with :body => /.*Hello.*/
|
260
|
+
send(method, "/hello_world").should_not respond_with :body => /.*incorrect.*/
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe "exact match on multipart response body" do
|
265
|
+
it "should match with successful response" do
|
266
|
+
send(method, "/multipart", :max_chunks => 2).should respond_with :body => "part1part2"
|
267
|
+
send(method, "/multipart", :max_chunks => 2).should_not respond_with :body => "whatever"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
describe "regex match on multipart response body" do
|
271
|
+
it "should match the body" do
|
272
|
+
send(method, "/multipart", :max_chunks => 2).should respond_with :body => /.*part.*/
|
273
|
+
send(method, "/multipart", :max_chunks => 2).should_not respond_with :body => /.*incorrect.*/
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "exact match on response body chunks" do
|
278
|
+
it "should match with successful response" do
|
279
|
+
send(method, "/multipart", :max_chunks => 2).should respond_with :chunks => ["part1", "part2"]
|
280
|
+
send(method, "/multipart", :max_chunks => 2).should_not respond_with :chunks => ["whatever1", "whatever2"]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
describe "regex match on response body chunks" do
|
284
|
+
# Note: In theory, an exact match would also work but because the content also contains an event-id,
|
285
|
+
# it is not practical.
|
286
|
+
it "should match with successful response" do
|
287
|
+
send(method, "/multipart", :max_chunks => 2).should respond_with :chunks => [/part1/, /part2/]
|
288
|
+
send(method, "/multipart", :max_chunks => 2).should_not respond_with :chunks => [/whatever1/, /whatever2/]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "multiple conditions" do
|
293
|
+
it "should match on status and body" do
|
294
|
+
send(method, "/200").should respond_with :status => :ok, :body => "ok"
|
295
|
+
send(method, "/200").should_not respond_with :status => :ok, :body => "incorrect"
|
296
|
+
send(method, "/200").should_not respond_with :status => :error, :body => "ok"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe "sse support" do
|
301
|
+
# Note: In theory, xact match would also work but because the content also contains an event-id,
|
302
|
+
# it is not practical.
|
303
|
+
it "should match with successful response" do
|
304
|
+
send(method, "/sse", :max_chunks => 2).should respond_with :chunks => [/^data: Hello 1.*/, /^data: Hello 2.*/]
|
305
|
+
send(method, "/sse", :max_chunks => 2).should_not respond_with :chunks => [/.*Incorrect 1.*/, /.*Incorrect 2.*/]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
it "should correctly handle exception in the callbacks" do
|
310
|
+
send(method, "/raise_on_start").should respond_with :status => 200 # Unfortunately, the headers have been already sent.
|
311
|
+
end
|
312
|
+
|
313
|
+
describe "when an action raises an exception" do
|
314
|
+
it "should handle error in before_start handler" do
|
315
|
+
get("/raise_before_start").should respond_with :status => 500
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should handle error in on_start handler" do
|
319
|
+
# Headers were already sent by the time the exception was raised.
|
320
|
+
get("/raise_on_start").should respond_with :body => /.*Error in on_start.*/, :status => 200
|
321
|
+
end
|
322
|
+
|
323
|
+
it "should handle error in on_finish handler" do
|
324
|
+
# Headers were already sent by the time the exception was raised.
|
325
|
+
get("/raise_on_finish").should respond_with :body => /.*Error in on_finish.*/, :status => 200
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
it "should support custom request headers" do
|
330
|
+
get("/request_headers", :headers => {"Custom1" => "ABC", "Custom2" => "DEF"}).should respond_with :body => /.*^Custom1: ABC$.*/i
|
331
|
+
get("/request_headers", :headers => {"Custom1" => "ABC", "Custom2" => "DEF"}).should respond_with :body => /.*^Custom2: DEF$.*/i
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should support request params" do
|
335
|
+
get("/request_params", :params => {:text => "Hello, world!"}).should respond_with :body => "Hello, world!"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# TODO Add method-specific paths to http routes and write specs.
|
340
|
+
describe "GET request" do
|
341
|
+
it_should_behave_like "async_request", :get
|
342
|
+
|
343
|
+
it "should be able to access paths accessible only with GET" do
|
344
|
+
get("/get_only").should respond_with :status => :ok
|
345
|
+
end
|
346
|
+
it "should not be able to access paths non-accessible with GET" do
|
347
|
+
get("/post_only").should respond_with :status => 405
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe "POST request" do
|
352
|
+
it_should_behave_like "async_request", :post
|
353
|
+
|
354
|
+
it "should be able to access paths accessible only with POST" do
|
355
|
+
post("/post_only").should respond_with :status => :ok
|
356
|
+
end
|
357
|
+
it "should not be able to access paths non-accessible with POST" do
|
358
|
+
post("/get_only").should respond_with :status => 405
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe "DELETE request" do
|
363
|
+
it_should_behave_like "async_request", :delete
|
364
|
+
|
365
|
+
it "should be able to access paths accessible only with DELETE" do
|
366
|
+
delete("/delete_only").should respond_with :status => :ok
|
367
|
+
end
|
368
|
+
it "should not be able to access paths non-accessible with DELETE" do
|
369
|
+
delete("/post_only").should respond_with :status => 405
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
describe "PUT request" do
|
374
|
+
it_should_behave_like "async_request", :put
|
375
|
+
|
376
|
+
it "should be able to access paths accessible only with PUT" do
|
377
|
+
put("/put_only").should respond_with :status => :ok
|
378
|
+
end
|
379
|
+
it "should not be able to access paths non-accessible with PUT" do
|
380
|
+
put("/post_only").should respond_with :status => 405
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# FIXME Better failure message for response matcher.
|
386
|
+
describe "'be_matching' matcher", :cramp => true do
|
387
|
+
def app
|
388
|
+
routes
|
389
|
+
end
|
390
|
+
|
391
|
+
# TODO Only basic matching here because respond_with matcher uses the same method.
|
392
|
+
# It should be the other way around, i.e. all the tests should be here but I hate to rewrite the specs now.
|
393
|
+
# Anyway, we need more tests here.
|
394
|
+
it "should support expectations on response status" do
|
395
|
+
get("/200") do |response|
|
396
|
+
response.should be_matching :status => 200
|
397
|
+
response.should be_matching :status => :ok
|
398
|
+
response.should_not be_matching :status => 500
|
399
|
+
stop
|
400
|
+
end
|
401
|
+
end
|
402
|
+
it "should support expectations on response body" do
|
403
|
+
get("/200") do |response|
|
404
|
+
response.read_body do
|
405
|
+
response.body.should == ["ok"]
|
406
|
+
response.body.should_not == ["whatever"]
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
it "should support array access" do
|
411
|
+
get("/200") do |response|
|
412
|
+
response[0].should == 200
|
413
|
+
response[1].should be_a Hash
|
414
|
+
response[2].should be_a Cramp::Body
|
415
|
+
response[-1].should be_a Cramp::Body
|
416
|
+
stop
|
417
|
+
end
|
418
|
+
end
|
419
|
+
it "should handle exceptions in block" do
|
420
|
+
lambda { get("/404") do |response|
|
421
|
+
raise "this is an error"
|
422
|
+
end }.should raise_error
|
423
|
+
end
|
424
|
+
|
425
|
+
describe "improper body access" do
|
426
|
+
it "should warn user if body is accessed via an attribute without read_body" do
|
427
|
+
get("/200") do |response|
|
428
|
+
lambda { response.body }.should raise_error
|
429
|
+
stop
|
430
|
+
end
|
431
|
+
end
|
432
|
+
it "should warn user if body is accessed via an attribute outside read_body" do
|
433
|
+
get("/200") do |response|
|
434
|
+
response.read_body
|
435
|
+
lambda { response.body }.should raise_error
|
436
|
+
stop
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "cramp"
|
3
|
+
require "rspec"
|
4
|
+
require "http_router"
|
5
|
+
|
6
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
7
|
+
require "rspec/cramp"
|
8
|
+
|
9
|
+
require "active_support/buffered_logger"
|
10
|
+
logger = ActiveSupport::BufferedLogger.new(File.join(File.dirname(__FILE__), "tests.log"))
|
11
|
+
logger.level = ActiveSupport::BufferedLogger::DEBUG
|
12
|
+
Cramp.logger = logger
|