arrow 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. metadata +339 -0
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
+ }
11
+
12
+ require 'spec'
13
+ require 'spec/lib/helpers'
14
+
15
+ require 'apache/fakerequest'
16
+
17
+ require 'arrow'
18
+ require 'arrow/transaction'
19
+
20
+
21
+ #####################################################################
22
+ ### C O N T E X T S
23
+ #####################################################################
24
+
25
+ describe Arrow::Transaction do
26
+ include Arrow::SpecHelpers
27
+
28
+ TEST_ACCEPT_HEADER = 'application/x-yaml, application/json; q=0.2, text/xml; q=0.75'
29
+
30
+
31
+ before( :all ) do
32
+ setup_logging( :crit )
33
+ end
34
+
35
+ before( :each ) do
36
+ @options = {}
37
+
38
+ @request = mock( "request object" )
39
+ @request.stub!( :options ).and_return( @options )
40
+ @request.stub!( :hostname ).and_return( 'testhost' )
41
+
42
+ @headers_in = Apache::Table.new
43
+ @headers_out = Apache::Table.new
44
+ @request.stub!( :headers_in ).and_return( @headers_in )
45
+ @request.stub!( :headers_out ).and_return( @headers_out )
46
+ @request.stub!( :sync_header= )
47
+ end
48
+
49
+
50
+ after( :all ) do
51
+ reset_logging()
52
+ end
53
+
54
+
55
+ it "knows it's dispatched from a handler mounted at / when its request " +
56
+ "has the root_dispatcher option set to 'yes'" do
57
+ @options['root_dispatcher'] = 'yes'
58
+ Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_true()
59
+ end
60
+
61
+ it "knows it's dispatched from a handler mounted at / when its request " +
62
+ "has the root_dispatcher option set to 'true'" do
63
+ @options['root_dispatcher'] = 'true'
64
+ Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_true()
65
+ end
66
+
67
+ it "knows it's dispatched from a handler mounted at / when its request " +
68
+ "has the root_dispatcher option set to '1'" do
69
+ @options['root_dispatcher'] = '1'
70
+ Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_true()
71
+ end
72
+
73
+ it "knows it's not dispatched from a handler mounted at / when its request " +
74
+ "has the root_dispatcher option set to 'false'" do
75
+ @options['root_dispatcher'] = 'false'
76
+ Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_false()
77
+ end
78
+
79
+ it "knows it's not dispatched from a handler mounted at / when its request " +
80
+ "has the root_dispatcher option set to 'no'" do
81
+ @options['root_dispatcher'] = 'no'
82
+ Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_false()
83
+ end
84
+
85
+ it "knows it's not dispatched from a handler mounted at / when its request " +
86
+ "has the root_dispatcher option set to '0'" do
87
+ @options['root_dispatcher'] = '0'
88
+ Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_false()
89
+ end
90
+
91
+ it "dispatched from a root dispatcher returns an empty string for the approot" do
92
+ @options['root_dispatcher'] = 'yes'
93
+ Arrow::Transaction.new( @request, nil, nil ).app_root.should == ''
94
+ end
95
+
96
+
97
+ describe " (an instance)" do
98
+
99
+ before( :each ) do
100
+ @txn = Arrow::Transaction.new( @request, nil, nil )
101
+ end
102
+
103
+
104
+ it "knows that a form was submitted if there's a urlencoded form content-type header with a POST" do
105
+ @headers_in[ 'content-type' ] = 'application/x-www-form-urlencoded'
106
+ @request.should_receive( :request_method ).at_least(1).and_return( 'POST' )
107
+ @txn.should be_a_form_request
108
+ end
109
+
110
+ it "knows that a form was submitted if there's a urlencoded form content-type header with a PUT" do
111
+ @headers_in[ 'content-type' ] = 'application/x-www-form-urlencoded'
112
+ @request.should_receive( :unparsed_uri ).and_return( '' ) # query is nil
113
+ @request.should_receive( :request_method ).and_return( 'PUT' )
114
+ @txn.should be_a_form_request
115
+ end
116
+
117
+ it "knows that a form was submitted if there's a urlencoded form content-type header with a GET" do
118
+ @request.should_receive( :unparsed_uri ).and_return( 'foo?bar=bas&biz=boz' )
119
+ @request.should_receive( :request_method ).and_return( 'GET' )
120
+ @txn.should be_a_form_request
121
+ end
122
+
123
+ it "knows that a form was submitted if there's a urlencoded form content-type header with a DELETE" do
124
+ @request.should_receive( :unparsed_uri ).and_return( 'foo?bar=bas&biz=boz' )
125
+ @request.should_receive( :request_method ).and_return( 'DELETE' )
126
+ @txn.should be_a_form_request
127
+ end
128
+
129
+
130
+ #it "knows that it wasn't served over secure transport if its request's schema isn't 'https'" do
131
+ # @request.should_receive( :unparsed_uri ).and_return( )
132
+ #end
133
+
134
+
135
+ it "should indicate a successful response when the status is 200" do
136
+ @request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_OK )
137
+ @txn.is_success?.should be_true
138
+ end
139
+
140
+ it "should indicate a successful response when the status is 201" do
141
+ @request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_CREATED )
142
+ @txn.is_success?.should be_true
143
+ end
144
+
145
+ it "should indicate a successful response when the status is 202" do
146
+ @request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_ACCEPTED )
147
+ @txn.is_success?.should be_true
148
+ end
149
+
150
+ it "should indicate a non-successful response when the status is 302" do
151
+ @request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_MOVED_TEMPORARILY )
152
+ @txn.is_success?.should_not be_true
153
+ end
154
+
155
+ it "should set its Apache status to REDIRECT when #redirect is called" do
156
+ @request.should_receive( :status= ).with( Apache::HTTP_MOVED_TEMPORARILY )
157
+ @txn.redirect( 'http://example.com/something' )
158
+ @txn.handler_status.should == Apache::REDIRECT
159
+ end
160
+
161
+ it "delegates to the request for request methods" do
162
+ @request.should_receive( :allowed ).and_return( :yep )
163
+ @txn.allowed.should == :yep
164
+ end
165
+
166
+
167
+ it "returns the X-Forwarded-Host header if present for the value returned by #proxied_host" do
168
+ @headers_in[ 'X-Forwarded-Host' ] = 'foo.bar.com'
169
+ @txn.proxied_host.should == 'foo.bar.com'
170
+ end
171
+
172
+ it "returns the X-Forwarded-Server header if X-Forwarded-Host is not " +
173
+ "present for the value returned by #proxied_host" do
174
+ @headers_in[ 'X-Forwarded-Server' ] = 'foo.bar.com'
175
+ @txn.proxied_host.should == 'foo.bar.com'
176
+ end
177
+
178
+
179
+ it "uses the proxy header for #construct_url" do
180
+ @headers_in[ 'X-Forwarded-Host' ] = 'foo.bar.com'
181
+ @headers_in[ 'X-Forwarded-Server' ] = 'foo.bar.com'
182
+
183
+ @request.should_receive( :construct_url ).and_return( 'http://hostname/bar' )
184
+
185
+ @txn.construct_url( "/bar" ).should == 'http://foo.bar.com/bar'
186
+ end
187
+
188
+ it "knows when the transaction is requested via XHR by the X-Requested-With header" do
189
+ @headers_in[ 'X-Requested-With' ] = 'XMLHttpRequest'
190
+ @txn.is_ajax_request?.should be_true()
191
+ end
192
+
193
+
194
+ it "knows when the transaction is not requested via XHR by the absence " +
195
+ "of an X-Requested-With header" do
196
+ @txn.is_ajax_request?.should be_false()
197
+ end
198
+
199
+ it "knows when the transaction is not requested via XHR by a non-AJAX " +
200
+ "X-Requested-With header" do
201
+ @headers_in[ 'X-Requested-With' ] = 'magic jellybeans of doom'
202
+ @txn.is_ajax_request?.should be_false()
203
+ end
204
+
205
+
206
+ it "returns cookies from its headers as an Arrow::CookieSet" do
207
+ @headers_in[ 'Cookie' ] = 'foo=12'
208
+
209
+ # Cookies are parsed on transaction creation, so we can't use the
210
+ # transaction that's created in the before(:each)
211
+ txn = Arrow::Transaction.new( @request, nil, nil )
212
+
213
+ txn.request_cookies.should be_an_instance_of( Arrow::CookieSet )
214
+ txn.request_cookies.should include( 'foo' )
215
+ txn.request_cookies['foo'].should be_an_instance_of( Arrow::Cookie )
216
+ end
217
+
218
+ it "adds Cookie headers for each cookie in a successful response" do
219
+ @request.should_receive( :status ).
220
+ at_least(:once).
221
+ and_return( Apache::HTTP_OK )
222
+
223
+ @headers_out.should_receive( :[]= ).with( 'Set-Cookie', /glah=locke/i )
224
+ @headers_out.should_receive( :[]= ).with( 'Set-Cookie', /foo=bar/i )
225
+ @headers_out.should_receive( :[]= ).with( 'Set-Cookie', /pants=velcro/i )
226
+
227
+ @txn.cookies['glah'] = 'locke'
228
+ @txn.cookies['foo'] = 'bar'
229
+ @txn.cookies['pants'] = 'velcro!'
230
+ @txn.cookies['pants'].expires = 'Sat Nov 12 22:04:00 1955'
231
+
232
+ @txn.add_cookie_headers
233
+ end
234
+
235
+ it "adds Cookie error headers for each cookie in an non-OK response" do
236
+ output_headers = mock( "output headers", :null_object => true )
237
+ err_output_headers = mock( "error output headers", :null_object => true )
238
+ @request.should_not_receive( :headers_out )
239
+ @request.should_receive( :err_headers_out ).
240
+ at_least(:once).
241
+ and_return( err_output_headers )
242
+ @request.should_receive( :status ).
243
+ at_least(:once).
244
+ and_return( Apache::REDIRECT )
245
+
246
+ err_output_headers.should_receive( :[]= ).with( 'Set-Cookie', /glah=locke/i )
247
+ err_output_headers.should_receive( :[]= ).with( 'Set-Cookie', /foo=bar/i )
248
+ err_output_headers.should_receive( :[]= ).with( 'Set-Cookie', /pants=velcro/i )
249
+
250
+ @txn.cookies['glah'] = 'locke'
251
+ @txn.cookies['foo'] = 'bar'
252
+ @txn.cookies['pants'] = 'velcro!'
253
+ @txn.cookies['pants'].expires = 'Sat Nov 12 22:04:00 1955'
254
+
255
+ @txn.add_cookie_headers
256
+ end
257
+
258
+ it "parses the 'Accept' header into one or more AcceptParam structs" do
259
+ @headers_in['Accept'] = TEST_ACCEPT_HEADER
260
+
261
+ @txn.accepted_types.should have( 3 ).members
262
+ @txn.accepted_types[0].mediatype.should == 'application/x-yaml'
263
+ @txn.accepted_types[1].mediatype.should == 'application/json'
264
+ @txn.accepted_types[2].mediatype.should == 'text/xml'
265
+ end
266
+
267
+ it "knows what mimetypes are acceptable responses" do
268
+ @headers_in[ 'Accept' ] = 'text/html, text/plain; q=0.5, image/*;q=0.1'
269
+
270
+ @txn.accepts?( 'text/html' ).should be_true()
271
+ @txn.accepts?( 'text/plain' ).should be_true()
272
+ @txn.accepts?( 'text/ascii' ).should be_false()
273
+ @txn.accepts?( 'image/png' ).should be_true()
274
+ @txn.accepts?( 'application/x-yaml' ).should be_false()
275
+ end
276
+
277
+
278
+ it "knows what mimetypes are explicitly acceptable responses" do
279
+ @headers_in[ 'Accept' ] = 'text/html, text/plain; q=0.5, image/*;q=0.1, */*'
280
+
281
+ @txn.explicitly_accepts?( 'text/html' ).should be_true()
282
+ @txn.explicitly_accepts?( 'text/plain' ).should be_true()
283
+ @txn.explicitly_accepts?( 'text/ascii' ).should be_false()
284
+ @txn.explicitly_accepts?( 'image/png' ).should be_false()
285
+ @txn.explicitly_accepts?( 'application/x-yaml' ).should be_false()
286
+ end
287
+
288
+
289
+ it "accepts anything if the client doesn't provide an Accept header" do
290
+ @txn.accepts?( 'text/html' ).should be_true()
291
+ @txn.accepts?( 'text/plain' ).should be_true()
292
+ @txn.accepts?( 'text/ascii' ).should be_true()
293
+ @txn.accepts?( 'image/png' ).should be_true()
294
+ @txn.accepts?( 'application/x-yaml' ).should be_true()
295
+ end
296
+
297
+ it "knows that the request accepts HTML if its Accept: header indicates it accepts " +
298
+ "'text/html'" do
299
+ @headers_in[ 'Accept' ] = 'text/html, text/plain; q=0.5, image/*;q=0.1'
300
+ @txn.accepts_html?.should be_true()
301
+ end
302
+
303
+ it "knows that the request accepts HTML if its Accept: header indicates it accepts " +
304
+ "'application/xhtml+xml'" do
305
+ @headers_in[ 'Accept' ] = 'application/xhtml+xml, text/plain; q=0.5, image/*;q=0.1'
306
+ @txn.accepts_html?.should be_true()
307
+ end
308
+
309
+ it "knows that the request doesn't accept HTML if its Accept: header indicates it doesn't" do
310
+ @headers_in[ 'Accept' ] = 'text/plain; q=0.5, image/*;q=0.1'
311
+ @txn.accepts_html?.should be_false()
312
+ end
313
+
314
+
315
+ end
316
+ end
317
+
318
+
319
+ # vim: set nosta noet ts=4 sw=4:
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
+ }
11
+
12
+
13
+ require 'spec'
14
+ require 'spec/lib/constants'
15
+ require 'spec/lib/helpers'
16
+
17
+ require 'arrow'
18
+
19
+
20
+ #####################################################################
21
+ ### C O N T E X T S
22
+ #####################################################################
23
+
24
+ describe Arrow do
25
+ include Arrow::SpecHelpers
26
+
27
+ it "returns a version string if asked" do
28
+ Arrow.version_string.should =~ /\w+ [\d.]+/
29
+ end
30
+
31
+
32
+ it "returns a version string with a build number if asked" do
33
+ Arrow.version_string(true).should =~ /\w+ [\d.]+ \(build [[:xdigit:]]+\)/
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # These are a collection of RSpec matchers to make unit testing of Arrow
4
+ # applets easier.
5
+
6
+ require 'apache/fakerequest'
7
+ require 'arrow'
8
+
9
+ require 'spec/matchers'
10
+
11
+ ### Fixturing functions
12
+ module Arrow::AppletMatchers
13
+
14
+ STATUS_NAMES = {
15
+ 100 => 'CONTINUE',
16
+ 101 => 'SWITCHING_PROTOCOLS',
17
+ 102 => 'PROCESSING',
18
+
19
+ 200 => 'OK',
20
+ 201 => 'CREATED',
21
+ 202 => 'ACCEPTED',
22
+ 203 => 'NON_AUTHORITATIVE',
23
+ 204 => 'NO_CONTENT',
24
+ 205 => 'RESET_CONTENT',
25
+ 206 => 'PARTIAL_CONTENT',
26
+ 207 => 'MULTI_STATUS',
27
+
28
+ 300 => 'MULTIPLE_CHOICES',
29
+ 301 => 'MOVED',
30
+ 302 => 'REDIRECT',
31
+ 303 => 'SEE_OTHER',
32
+ 304 => 'NOT_MODIFIED',
33
+ 305 => 'USE_PROXY',
34
+ 307 => 'TEMPORARY_REDIRECT',
35
+
36
+ 400 => 'BAD_REQUEST',
37
+ 401 => 'UNAUTHORIZED',
38
+ 402 => 'PAYMENT_REQUIRED',
39
+ 403 => 'FORBIDDEN',
40
+ 404 => 'NOT_FOUND',
41
+ 405 => 'METHOD_NOT_ALLOWED',
42
+ 406 => 'NOT_ACCEPTABLE',
43
+ 407 => 'PROXY_AUTHENTICATION_REQUIRED',
44
+ 408 => 'REQUEST_TIME_OUT',
45
+ 409 => 'CONFLICT',
46
+ 410 => 'GONE',
47
+ 411 => 'LENGTH_REQUIRED',
48
+ 412 => 'PRECONDITION_FAILED',
49
+ 413 => 'REQUEST_ENTITY_TOO_LARGE',
50
+ 414 => 'REQUEST_URI_TOO_LARGE',
51
+ 415 => 'UNSUPPORTED_MEDIA_TYPE',
52
+ 416 => 'RANGE_NOT_SATISFIABLE',
53
+ 417 => 'EXPECTATION_FAILED',
54
+ 422 => 'UNPROCESSABLE_ENTITY',
55
+ 423 => 'LOCKED',
56
+ 424 => 'FAILED_DEPENDENCY',
57
+
58
+ 500 => 'SERVER_ERROR',
59
+ 501 => 'NOT_IMPLEMENTED',
60
+ 502 => 'BAD_GATEWAY',
61
+ 503 => 'SERVICE_UNAVAILABLE',
62
+ 504 => 'GATEWAY_TIME_OUT',
63
+ 505 => 'VERSION_NOT_SUPPORTED',
64
+ 506 => 'VARIANT_ALSO_VARIES',
65
+ 507 => 'INSUFFICIENT_STORAGE',
66
+ 510 => 'NOT_EXTENDED',
67
+ }
68
+
69
+
70
+ ### A matcher that wraps an applet's #run method and can assert things about the
71
+ ### generated response.
72
+ class RunWithMatcher
73
+
74
+ ### Create a new FinishWithMatcher that should finish with the given
75
+ ###
76
+ def initialize( status, message=:any_message )
77
+ @expected_status = status
78
+ @expected_message = message
79
+ @actual = nil
80
+ end
81
+
82
+
83
+ ### Returns true if the code executed in the given +procobj+ calls
84
+ ### 'finish_with'
85
+ def matches?( procobj )
86
+ begin
87
+ @actual = catch( :finish, &procobj )
88
+ rescue => err
89
+ @actual = err
90
+ ensure
91
+ return statuses_match? && messages_match?
92
+ end
93
+ end
94
+
95
+ ### Returns true if the expected status is the same as the actual
96
+ ### status
97
+ def statuses_match?
98
+ return @actual.is_a?( Hash ) &&
99
+ @actual.key?( :status ) &&
100
+ @actual[:status] == @expected_status
101
+ end
102
+
103
+ ### Returns true if the expected message matches the actual message
104
+ def messages_match?
105
+ return false unless @actual.is_a?( Hash ) && @actual.key?( :message )
106
+ return true if @expected_message == :any_message
107
+
108
+ case @expected_message
109
+ when String
110
+ return @actual[:message] == @expected_message
111
+ when Regexp
112
+ return @actual[:message] =~ @expected_message
113
+ else
114
+ return false
115
+ end
116
+ end
117
+
118
+
119
+ ### Generate an appropriate failure message for a #should match
120
+ ### failure.
121
+ def failure_message
122
+ if @actual
123
+ return "expected %s, got %p" % [
124
+ self.expectation_description,
125
+ @actual
126
+ ]
127
+ else
128
+ return
129
+ end
130
+ end
131
+
132
+
133
+ ### Generate an appropriate failure message for a #should_not match
134
+ ### failure.
135
+ def negative_failure_message
136
+ if @expected_status
137
+ return "expected %s" % self.description
138
+ else
139
+ return "expected a normal return, but applet finished with %d (%s): %s"
140
+ end
141
+ end
142
+
143
+
144
+ ### Return a human-readable description of the matcher
145
+ def description
146
+ return "the action to %s" % self.expectation_description
147
+ end
148
+
149
+
150
+ ### Return a description of the expected status and message.
151
+ def expectation_description
152
+ if @expected_message.is_a?( Regexp )
153
+ return "finish_with %d (%s) status and a message which matches %p" % [
154
+ @expected_status,
155
+ STATUS_NAMES[@expected_status],
156
+ @expected_message
157
+ ]
158
+ else
159
+ return "finish_with %d (%s) status and a message which is %p" % [
160
+ @expected_status,
161
+ STATUS_NAMES[@expected_status],
162
+ @expected_message
163
+ ]
164
+ end
165
+ end
166
+ end
167
+
168
+
169
+ ### A matcher that wraps an action method and can trap abnormal responses
170
+ class FinishWithMatcher
171
+
172
+ ### Create a new FinishWithMatcher that should finish with the given
173
+ ###
174
+ def initialize( status, message=:any_message )
175
+ @expected_status = status
176
+ @expected_message = message
177
+ @actual = nil
178
+ end
179
+
180
+
181
+ ### Returns true if the code executed in the given +procobj+ calls
182
+ ### 'finish_with'
183
+ def matches?( procobj )
184
+ begin
185
+ @actual = catch( :finish, &procobj )
186
+ rescue => err
187
+ @actual = err
188
+ ensure
189
+ return statuses_match? && messages_match?
190
+ end
191
+ end
192
+
193
+ ### Returns true if the expected status is the same as the actual
194
+ ### status
195
+ def statuses_match?
196
+ return @actual.is_a?( Hash ) &&
197
+ @actual.key?( :status ) &&
198
+ @actual[:status] == @expected_status
199
+ end
200
+
201
+ ### Returns true if the expected message matches the actual message
202
+ def messages_match?
203
+ return false unless @actual.is_a?( Hash ) && @actual.key?( :message )
204
+ return true if @expected_message == :any_message
205
+
206
+ case @expected_message
207
+ when String
208
+ return @actual[:message] == @expected_message
209
+ when Regexp
210
+ return @actual[:message] =~ @expected_message
211
+ else
212
+ return false
213
+ end
214
+ end
215
+
216
+
217
+ ### Generate an appropriate failure message for a #should match
218
+ ### failure.
219
+ def failure_message
220
+ if @actual
221
+ return "expected %s, got %p" % [
222
+ self.expectation_description,
223
+ @actual
224
+ ]
225
+ else
226
+ return
227
+ end
228
+ end
229
+
230
+
231
+ ### Generate an appropriate failure message for a #should_not match
232
+ ### failure.
233
+ def negative_failure_message
234
+ if @expected_status
235
+ return "expected %s" % self.description
236
+ else
237
+ return "expected a normal return, but applet finished with %d (%s): %s"
238
+ end
239
+ end
240
+
241
+
242
+ ### Return a human-readable description of the matcher
243
+ def description
244
+ return "the action to %s" % self.expectation_description
245
+ end
246
+
247
+
248
+ ### Return a description of the expected status and message.
249
+ def expectation_description
250
+ if @expected_message.is_a?( Regexp )
251
+ return "finish_with %d (%s) status and a message which matches %p" % [
252
+ @expected_status,
253
+ STATUS_NAMES[@expected_status],
254
+ @expected_message
255
+ ]
256
+ else
257
+ return "finish_with %d (%s) status and a message which is %p" % [
258
+ @expected_status,
259
+ STATUS_NAMES[@expected_status],
260
+ @expected_message
261
+ ]
262
+ end
263
+ end
264
+ end
265
+
266
+
267
+ ### The main matcher expression. Assert that the given block is exited via a thrown status
268
+ ### code of +status+ and a message which matches +message+ (if given).
269
+ def run_with( status, message=:any_message )
270
+ return Arrow::AppletMatchers::RunWithMatcher.new( status, message )
271
+ end
272
+
273
+ ### The action method matcher expression. Assert that the given block is exited via a
274
+ ### thrown status code of +status+ and a message which matches +message+ (if given).
275
+ def finish_with( status, message=:any_message )
276
+ return Arrow::AppletMatchers::FinishWithMatcher.new( status, message )
277
+ end
278
+
279
+ end
280
+
281
+