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.
@@ -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