rspec-cramp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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