arrow 1.0.7

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