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,645 @@
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
+
13
+ require 'spec'
14
+ require 'spec/lib/constants'
15
+ require 'spec/lib/helpers'
16
+ require 'spec/lib/appletmatchers'
17
+
18
+ require 'arrow'
19
+ require 'arrow/service'
20
+ require 'arrow/acceptparam'
21
+
22
+
23
+ include Arrow::TestConstants
24
+
25
+ #####################################################################
26
+ ### C O N T E X T S
27
+ #####################################################################
28
+
29
+ describe Arrow::Service do
30
+ include Arrow::SpecHelpers,
31
+ Arrow::AppletMatchers
32
+
33
+ before( :all ) do
34
+ setup_logging( :crit )
35
+ end
36
+
37
+ after( :all ) do
38
+ reset_logging()
39
+ end
40
+
41
+
42
+ before( :each ) do
43
+ @config = stub( "Config" )
44
+ @factory = stub( "Template factory" )
45
+ @uri = '/service/test'
46
+ @accepted_types = [ Arrow::AcceptParam.parse('*/*') ]
47
+
48
+ @txn = mock( "transaction" )
49
+ @txn.stub!( :vargs ).and_return( nil )
50
+ @txn.stub!( :accepted_types ).and_return( @accepted_types )
51
+ @txn.stub!( :form_request? ).and_return( false )
52
+ @txn.stub!( :vargs= )
53
+ end
54
+
55
+
56
+ describe " subclass" do
57
+ before( :all ) do
58
+ @service_class = Class.new( Arrow::Service ) do
59
+ public :deserialize_request_body
60
+ end
61
+ end
62
+
63
+ before( :each ) do
64
+ @request_headers = mock( "request headers" )
65
+ @service = @service_class.new( @config, @factory, @uri )
66
+ @txn.stub!( :request_method ).and_return( 'POST' )
67
+ @txn.stub!( :headers_in ).and_return( @request_headers )
68
+ end
69
+
70
+ it "returns an UNSUPPORTED MEDIA TYPE response if given a body in an unsupported format" do
71
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
72
+ and_return( 'application/x-bungtruck' )
73
+
74
+ lambda {
75
+ @service.deserialize_request_body( @txn )
76
+ }.should finish_with( Apache::HTTP_UNSUPPORTED_MEDIA_TYPE, /don't know how to handle/i )
77
+ end
78
+
79
+ it "knows how to deserialize incoming request bodies in JSON format" do
80
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
81
+ and_return( 'application/json' )
82
+
83
+ JSON.should_receive( :load ).with( @txn ).and_return( :ruby_objects )
84
+ @service.deserialize_request_body( @txn ).should == :ruby_objects
85
+ end
86
+
87
+ it "knows how to deserialize incoming single values from request bodies in JSON format" do
88
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
89
+ and_return( 'application/json' )
90
+
91
+ special_key = Arrow::Service::SPECIAL_JSON_KEY
92
+
93
+ JSON.should_receive( :load ).with( @txn ).and_return({ special_key => 'single_value' })
94
+ @service.deserialize_request_body( @txn ).should == 'single_value'
95
+ end
96
+
97
+ it "doesn't try to unwrap single-value hashes" do
98
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
99
+ and_return( 'application/json' )
100
+
101
+ special_key = Arrow::Service::SPECIAL_JSON_KEY
102
+ structure = {
103
+ 'non-special-key' => 'a second value'
104
+ }
105
+
106
+ JSON.should_receive( :load ).with( @txn ).and_return( structure )
107
+ @service.deserialize_request_body( @txn ).should == structure
108
+ end
109
+
110
+ it "doesn't try to unwrap hashes that contain the special key, but aren't just one pair" do
111
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
112
+ and_return( 'application/json' )
113
+
114
+ special_key = Arrow::Service::SPECIAL_JSON_KEY
115
+ structure = {
116
+ special_key => 'single value',
117
+ 'non-special-key' => 'a second value'
118
+ }
119
+
120
+ JSON.should_receive( :load ).with( @txn ).and_return( structure )
121
+ @service.deserialize_request_body( @txn ).should == structure
122
+ end
123
+
124
+ it "knows how to deserialize incoming request bodies in JSON format with a charset" do
125
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
126
+ and_return( 'application/json; charset=UTF-8' )
127
+
128
+ JSON.should_receive( :load ).with( @txn ).and_return( :ruby_objects )
129
+ @service.deserialize_request_body( @txn ).should == :ruby_objects
130
+ end
131
+
132
+ it "knows how to deserialize incoming request bodies in YAML format" do
133
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
134
+ and_return( 'text/x-yaml' )
135
+
136
+ YAML.should_receive( :load ).with( @txn ).and_return( :ruby_objects )
137
+ @service.deserialize_request_body( @txn ).should == :ruby_objects
138
+ end
139
+
140
+ it "knows how to deserialize incoming request bodies in marshalled format" do
141
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
142
+ and_return( RUBY_MARSHALLED_MIMETYPE )
143
+ Marshal.should_receive( :load ).with( @txn ).and_return( :ruby_objects )
144
+ @service.deserialize_request_body( @txn ).should == :ruby_objects
145
+ end
146
+
147
+ it "knows how to deserialize incoming request bodies in " +
148
+ "'application/x-www-form-urlencoded' format" do
149
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
150
+ and_return( 'application/x-www-form-urlencoded' )
151
+ @txn.should_receive( :all_params ).and_return( :form_fields )
152
+
153
+ @service.deserialize_request_body( @txn ).should == :form_fields
154
+ end
155
+
156
+ it "knows how to deserialize incoming request bodies in 'multipart/form-data' format" do
157
+ @request_headers.should_receive( :[] ).with( 'content-type' ).
158
+ and_return( 'multipart/form-data' )
159
+ @txn.should_receive( :all_params ).and_return( :form_fields )
160
+
161
+ @service.deserialize_request_body( @txn ).should == :form_fields
162
+ end
163
+ end
164
+
165
+ describe " subclass which doesn't provide implementations of any operation" do
166
+
167
+ before( :all ) do
168
+ @service_class = Class.new( Arrow::Service )
169
+ end
170
+
171
+ before( :each ) do
172
+ @service = @service_class.new( @config, @factory, @uri )
173
+ @txn.stub!( :uri ).and_return( @uri )
174
+ @txn.stub!( :accepts? ).and_return( true )
175
+ @headers = mock( "headers table" )
176
+ @txn.stub!( :headers_out ).and_return( @headers )
177
+ @err_headers = mock( "headers table" )
178
+ @txn.stub!( :err_headers_out ).and_return( @err_headers )
179
+ @txn.stub!( :content_type ).and_return( 'application/x-rubyobject' )
180
+ @txn.stub!( :normalized_accept_string ).and_return( "acceptable types" )
181
+ @txn.stub!( :explicitly_accepts? ).and_return( true )
182
+ @txn.stub!( :status ).and_return( nil )
183
+ end
184
+
185
+ it "knows that it doesn't implement any HTTP method except OPTIONS" do
186
+ headers_out = mock( "headers table" )
187
+ @txn.stub!( :request_method ).and_return( 'OPTIONS' )
188
+
189
+ @txn.should_receive( :headers_out ).and_return( headers_out )
190
+ headers_out.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
191
+ @txn.should_receive( :content_type= ).with( RUBY_OBJECT_MIMETYPE )
192
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
193
+
194
+ @service.run( @txn ).should == ['OPTIONS']
195
+ end
196
+
197
+ it "maps a GET to #not_allowed" do
198
+ @txn.stub!( :request_method ).and_return( 'GET' )
199
+
200
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
201
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
202
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
203
+
204
+ @service.run( @txn ).should =~ /GET is not allowed/i
205
+ end
206
+
207
+ it "maps a GET with an ID to #not_allowed" do
208
+ @txn.stub!( :request_method ).and_return( 'GET' )
209
+ @txn.stub!( :uri ).and_return( @uri + '/18' )
210
+
211
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
212
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
213
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
214
+
215
+ @service.run( @txn ).should =~ /GET is not allowed/i
216
+ end
217
+
218
+ it "maps a HEAD without an ID to #not_allowed" do
219
+ @txn.stub!( :request_method ).and_return( 'HEAD' )
220
+
221
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
222
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
223
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
224
+
225
+ @service.run( @txn ).should =~ /HEAD is not allowed/i
226
+ end
227
+
228
+ it "maps a HEAD with an ID to #not_allowed" do
229
+ @txn.stub!( :request_method ).and_return( 'HEAD' )
230
+ @txn.stub!( :uri ).and_return( @uri + '/18' )
231
+
232
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
233
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
234
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
235
+
236
+ @service.run( @txn ).should =~ /HEAD is not allowed/i
237
+ end
238
+
239
+ it "maps a POST to #not_allowed" do
240
+ @txn.stub!( :request_method ).and_return( 'POST' )
241
+
242
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
243
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
244
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
245
+
246
+ @service.run( @txn ).should =~ /POST is not allowed/i
247
+ end
248
+
249
+ it "maps a PUT to #not_allowed" do
250
+ @err_headers = mock( "error headers table" )
251
+ @txn.stub!( :err_headers_out ).and_return( @err_headers )
252
+ @txn.stub!( :request_method ).and_return( 'PUT' )
253
+
254
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
255
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
256
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
257
+
258
+ @service.run( @txn ).should =~ /PUT is not allowed/i
259
+ end
260
+
261
+ it "maps a DELETE to #not_allowed" do
262
+ @err_headers = mock( "error headers table" )
263
+ @txn.stub!( :err_headers_out ).and_return( @err_headers )
264
+ @txn.stub!( :request_method ).and_return( 'DELETE' )
265
+
266
+ @err_headers.should_receive( :[]= ).with( 'Allow', 'OPTIONS' )
267
+ @txn.should_receive( :status= ).with( Apache::METHOD_NOT_ALLOWED )
268
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
269
+
270
+ @service.run( @txn ).should =~ /DELETE is not allowed/i
271
+ end
272
+
273
+ end
274
+
275
+ describe " subclass which provides implementations of all operations" do
276
+ before( :all ) do
277
+ @service_class = Class.new( Arrow::Service ) do
278
+ def fetch( txn, id ); :one_resource; end
279
+ def fetch_all( txn ); :resource_collection; end
280
+ def create( txn, id=nil ); :new_resource; end
281
+ def update( txn, id ); :updated_resource; end
282
+ def update_all( txn ); :updated_collection; end
283
+ def delete( txn, id ); :deleted_resource; end
284
+ def delete_all( txn ); :deleted_collection; end
285
+ end
286
+ end
287
+
288
+ before( :each ) do
289
+ @service = @service_class.new( @config, @factory, @uri )
290
+ @txn.stub!( :uri ).and_return( @uri )
291
+ @txn.stub!( :content_type ).and_return( 'application/x-rubyobject' )
292
+ @txn.stub!( :accepts? ).and_return( true )
293
+ @txn.stub!( :normalized_accept_string ).and_return( "acceptable types" )
294
+ @txn.stub!( :status ).and_return( nil )
295
+
296
+ @headers = mock( "headers table" )
297
+ @txn.stub!( :headers_out ).and_return( @headers )
298
+ end
299
+
300
+
301
+ it "knows that it implements all HTTP methods including OPTIONS" do
302
+ @txn.stub!( :request_method ).and_return( 'OPTIONS' )
303
+
304
+ @headers.should_receive( :[]= ).with( 'Allow', 'DELETE, GET, HEAD, OPTIONS, POST, PUT' )
305
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
306
+ @txn.should_receive( :content_type= ).with( RUBY_OBJECT_MIMETYPE )
307
+
308
+ @service.run( @txn ).should == %w[DELETE GET HEAD OPTIONS POST PUT]
309
+ end
310
+
311
+ it "maps a GET without an ID to #fetch_all" do
312
+ @txn.stub!( :request_method ).and_return( 'GET' )
313
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
314
+ @service.run( @txn ).should == :resource_collection
315
+ end
316
+
317
+ it "maps a GET with an ID to #fetch" do
318
+ @txn.stub!( :request_method ).and_return( 'GET' )
319
+ @txn.stub!( :uri ).and_return( @uri + '/199' )
320
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
321
+ @service.run( @txn, '199' ).should == :one_resource
322
+ end
323
+
324
+ it "maps a POST without an ID to #create" do
325
+ @txn.stub!( :request_method ).and_return( 'POST' )
326
+ @txn.stub!( :uri ).and_return( @uri )
327
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
328
+ @service.run( @txn ).should == :new_resource
329
+ end
330
+
331
+ it "maps a POST with an ID to #create" do
332
+ @txn.stub!( :request_method ).and_return( 'POST' )
333
+ @txn.stub!( :uri ).and_return( @uri + '/199' )
334
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
335
+ @service.run( @txn, '199' ).should == :new_resource
336
+ end
337
+
338
+ it "maps a PUT without an ID to #update_all" do
339
+ @txn.stub!( :request_method ).and_return( 'PUT' )
340
+ @txn.stub!( :uri ).and_return( @uri )
341
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
342
+ @service.run( @txn ).should == :updated_collection
343
+ end
344
+
345
+ it "maps a PUT with an ID to #update" do
346
+ @txn.stub!( :request_method ).and_return( 'PUT' )
347
+ @txn.stub!( :uri ).and_return( @uri + '/199' )
348
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
349
+ @service.run( @txn, '199' ).should == :updated_resource
350
+ end
351
+
352
+ it "maps a DELETE without an ID to #delete_all" do
353
+ @txn.stub!( :request_method ).and_return( 'DELETE' )
354
+ @txn.stub!( :uri ).and_return( @uri )
355
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
356
+ @service.run( @txn ).should == :deleted_collection
357
+ end
358
+
359
+ it "maps a DELETE with an ID to #delete" do
360
+ @txn.stub!( :request_method ).and_return( 'DELETE' )
361
+ @txn.stub!( :uri ).and_return( @uri + '/199' )
362
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
363
+ @service.run( @txn, '199' ).should == :deleted_resource
364
+ end
365
+
366
+ end
367
+
368
+ describe " subclass which supports resource operations" do
369
+ before( :all ) do
370
+ @service_class = Class.new( Arrow::Service ) do
371
+ def options_test( txn, id ); :test_options; end
372
+ def fetch_test( txn, id ); :one_resource_test; end
373
+ def fetch_test_subresource( txn, id ); :one_resource_test_subresource; end
374
+ def create_test( txn, id=nil ); :new_resource_test; end
375
+ def update_test( txn, id ); :updated_resource_test; end
376
+ def delete_test( txn, id ); :deleted_resource_test; end
377
+ def delete_test_subresource( txn, id ); :deleted_resource_test_subresource; end
378
+ def validate_id( id ); return id; end
379
+ end
380
+ end
381
+
382
+ before( :each ) do
383
+ @service = @service_class.new( @config, @factory, @uri )
384
+ @txn.stub!( :uri ).and_return( @uri )
385
+ @txn.stub!( :content_type ).and_return( 'application/x-rubyobject' )
386
+ @txn.stub!( :accepts? ).and_return( true )
387
+ @txn.stub!( :normalized_accept_string ).and_return( "acceptable types" )
388
+ @txn.stub!( :status ).and_return( nil )
389
+
390
+ @headers = mock( "headers table" )
391
+ @txn.stub!( :headers_out ).and_return( @headers )
392
+ end
393
+
394
+
395
+ it "maps an OPTIONS request with an ID and one operation to #options_«operation»" do
396
+ @txn.stub!( :request_method ).and_return( 'OPTIONS' )
397
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
398
+ @service.run( @txn, '199', 'test' ).should == :test_options
399
+ end
400
+
401
+ it "maps a GET with an ID and one operation to #fetch_«operation»" do
402
+ @txn.stub!( :request_method ).and_return( 'GET' )
403
+ @txn.stub!( :uri ).and_return( @uri + '/199/test' )
404
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
405
+ @service.run( @txn, '199', 'test' ).should == :one_resource_test
406
+ end
407
+
408
+ it "maps a GET with an ID and two operations to #fetch_«op1»_«op2»" do
409
+ @txn.stub!( :request_method ).and_return( 'GET' )
410
+ @txn.stub!( :uri ).and_return( @uri + '/199/test/subresource' )
411
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
412
+ @service.run( @txn, '199', 'test', 'subresource' ).
413
+ should == :one_resource_test_subresource
414
+ end
415
+
416
+ it "maps a DELETE with an ID and two operations to #fetch_«op1»_«op2»" do
417
+ @txn.stub!( :request_method ).and_return( 'DELETE' )
418
+ @txn.stub!( :uri ).and_return( @uri + '/199/test/subresource' )
419
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
420
+ @service.run( @txn, '199', 'test', 'subresource' ).
421
+ should == :deleted_resource_test_subresource
422
+ end
423
+
424
+ it "maps a POST with an ID and operation to #create_«operation»" do
425
+ @txn.stub!( :request_method ).and_return( 'POST' )
426
+ @txn.stub!( :uri ).and_return( @uri + '/199/test' )
427
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
428
+ @service.run( @txn, '199', 'test' ).should == :new_resource_test
429
+ end
430
+
431
+ it "maps a PUT with an ID and operation to #update_«operation»" do
432
+ @txn.stub!( :request_method ).and_return( 'PUT' )
433
+ @txn.stub!( :uri ).and_return( @uri + '/199/test' )
434
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
435
+ @service.run( @txn, '199', 'test' ).should == :updated_resource_test
436
+ end
437
+
438
+ it "maps a DELETE with an ID and operation to #delete_«operation»" do
439
+ @txn.stub!( :request_method ).and_return( 'DELETE' )
440
+ @txn.stub!( :uri ).and_return( @uri + '/fookles/test' )
441
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
442
+ @service.run( @txn, 'fookles', 'test' ).should == :deleted_resource_test
443
+ end
444
+
445
+ end
446
+
447
+ describe " subclass whose resources have (default) simple integer IDs" do
448
+
449
+ before( :all ) do
450
+ @service_class = Class.new( Arrow::Service ) do
451
+ def fetch( txn, id ); :one_resource; end
452
+ def fetch_all( txn ); :resource_collection; end
453
+ def create( txn, id=nil ); :new_resource; end
454
+ def update( txn ); :updated_resource; end
455
+ def update_all( txn ); :updated_collection; end
456
+ def delete( txn ); :deleted_resource; end
457
+ def delete_all( txn ); :deleted_collection; end
458
+ end
459
+ end
460
+
461
+ before( :each ) do
462
+ @service = @service_class.new( @config, @factory, @uri )
463
+ @txn.stub!( :uri ).and_return( @uri )
464
+ @txn.stub!( :content_type ).and_return( 'text/plain' )
465
+ @txn.stub!( :accepts? ).and_return( true )
466
+ @txn.stub!( :normalized_accept_string ).and_return( "acceptable types" )
467
+ end
468
+
469
+ it " returns a BAD_REQUEST response for a GET request with an invalid ID" do
470
+ @txn.stub!( :request_method ).and_return( 'GET' )
471
+ @txn.stub!( :explicitly_accepts? ).and_return( false )
472
+ @txn.stub!( :accepts_html? ).and_return( false )
473
+ @txn.should_receive( :status= ).with( Apache::BAD_REQUEST )
474
+ @txn.should_receive( :content_type= ).with( 'text/plain' )
475
+
476
+ @service.run( @txn, 'ryanisthebest' ).should =~ /invalid id/i
477
+ end
478
+
479
+ end
480
+
481
+ TEST_SERVICE_OBJECTS = [
482
+ {
483
+ :name => 'object1',
484
+ :key1 => 'value1',
485
+ :key2 => 'value2',
486
+ :key3 => 'value3',
487
+ },
488
+ {
489
+ :name => 'object2',
490
+ :key1 => 'value4',
491
+ :key2 => 'value5',
492
+ :key3 => 'value6',
493
+ },
494
+ ]
495
+
496
+ describe "subclass which returns Ruby objects" do
497
+ before( :all ) do
498
+ @service_class = Class.new( Arrow::Service ) do
499
+ def initialize( *args )
500
+ @objects = TEST_SERVICE_OBJECTS.dup
501
+ super
502
+ end
503
+
504
+ attr_reader :objects
505
+
506
+ def fetch( txn, id )
507
+ return @objects[ id - 1 ]
508
+ end
509
+ def fetch_all( txn )
510
+ return @objects
511
+ end
512
+ end
513
+ end
514
+
515
+ before( :each ) do
516
+ @request_headers = mock( "request headers" )
517
+ @response_headers = mock( "response headers" )
518
+ @error_headers = mock( "error headers" )
519
+
520
+ @service = @service_class.new( @config, @factory, @uri )
521
+
522
+ @txn.stub!( :uri ).and_return( @uri )
523
+ @txn.stub!( :request_method ).and_return( 'GET' )
524
+ @txn.stub!( :headers_out ).and_return( @response_headers )
525
+ @txn.stub!( :headers_in ).and_return( @request_headers )
526
+ @txn.stub!( :err_headers_out ).and_return( @error_headers )
527
+
528
+ @txn.stub!( :content_type ).and_return( nil )
529
+ @txn.stub!( :accepts? ).and_return( true )
530
+ @txn.stub!( :explicitly_accepts? ).and_return( false )
531
+ @txn.stub!( :normalized_accept_string ).and_return( "acceptable types" )
532
+ @txn.stub!( :status ).and_return( nil )
533
+ end
534
+
535
+ it "serves a single fetched object as HTML if the request doesn't specify acceptable " +
536
+ "content types" do
537
+ template = mock( "service template" ).as_null_object
538
+ @factory.should_receive( :get_template ).and_return( template )
539
+
540
+ @txn.stub!( :explicitly_accepts? ).and_return( false )
541
+ @txn.should_receive( :accepts_html? ).and_return( true )
542
+ @txn.should_receive( :content_type= ).with( HTML_MIMETYPE )
543
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
544
+ template.should_receive( :body= ).with( /value4/ )
545
+
546
+ @service.run( @txn, '2' ).should == template
547
+ end
548
+
549
+ it "serves an object collection as HTML if the request doesn't specify acceptable " +
550
+ "content types" do
551
+ template = mock( "service template" ).as_null_object
552
+ @factory.should_receive( :get_template ).and_return( template )
553
+
554
+ @txn.stub!( :explicitly_accepts? ).and_return( false )
555
+ @txn.should_receive( :accepts_html? ).and_return( true )
556
+ @txn.should_receive( :content_type= ).with( HTML_MIMETYPE )
557
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
558
+ template.should_receive( :body= ).with( /value4/ )
559
+
560
+ @service.run( @txn ).should == template
561
+ end
562
+
563
+ it "serves a single fetched object as JSON if the request prefers it" do
564
+ @txn.stub!( :explicitly_accepts? ).with( 'application/json' ).
565
+ and_return( true )
566
+ @txn.should_receive( :content_type= ).with( 'application/json' )
567
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
568
+
569
+ @service.objects[1].should_receive( :to_json ).and_return( :json_object2 )
570
+
571
+ @txn.should_not_receive( :accepts_html? ).and_return( true )
572
+
573
+ @service.run( @txn, '2' ).should == :json_object2
574
+ end
575
+
576
+ it "serves an object collection as JSON if the request prefers it" do
577
+ @txn.stub!( :explicitly_accepts? ).with( 'application/json' ).
578
+ and_return( true )
579
+ @txn.should_receive( :content_type= ).with( 'application/json' )
580
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
581
+
582
+ @txn.should_not_receive( :accepts_html? ).and_return( true )
583
+
584
+ @service.objects.should_receive( :to_json ).and_return( :json_object_collection )
585
+
586
+ @service.run( @txn ).should == :json_object_collection
587
+ end
588
+
589
+ it "serves a single fetched object as YAML if the request prefers it" do
590
+ @txn.stub!( :explicitly_accepts? ).with( 'text/x-yaml' ).
591
+ and_return( true )
592
+ @txn.should_receive( :content_type= ).with( 'text/x-yaml' )
593
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
594
+
595
+ @service.objects[1].should_receive( :to_yaml ).and_return( :yaml_object2 )
596
+
597
+ @txn.should_not_receive( :accepts_html? ).and_return( true )
598
+
599
+ @service.run( @txn, '2' ).should == :yaml_object2
600
+ end
601
+
602
+ it "serves an object collection as YAML if the request prefers it" do
603
+ @txn.stub!( :explicitly_accepts? ).with( 'text/x-yaml' ).
604
+ and_return( true )
605
+ @txn.should_receive( :content_type= ).with( 'text/x-yaml' )
606
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
607
+
608
+ @txn.should_not_receive( :accepts_html? ).and_return( true )
609
+
610
+ @service.objects.should_receive( :to_yaml ).and_return( :yaml_object_collection )
611
+
612
+ @service.run( @txn ).should == :yaml_object_collection
613
+ end
614
+
615
+ it "serves a single fetched object as XML if the request prefers it" do
616
+ @txn.stub!( :explicitly_accepts? ).with( 'application/xml+rubyobject' ).
617
+ and_return( true )
618
+ @txn.should_receive( :content_type= ).with( 'application/xml+rubyobject' )
619
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
620
+
621
+ @service.objects[1].should_receive( :to_xml ).and_return( :xml_object2 )
622
+
623
+ @txn.should_not_receive( :accepts_html? ).and_return( true )
624
+
625
+ @service.run( @txn, '2' ).should == :xml_object2
626
+ end
627
+
628
+ it "serves an object collection as XML if the request prefers it" do
629
+ @txn.stub!( :explicitly_accepts? ).with( 'application/xml+rubyobject' ).
630
+ and_return( true )
631
+ @txn.should_receive( :content_type= ).with( 'application/xml+rubyobject' )
632
+ @txn.should_receive( :status= ).with( Apache::HTTP_OK )
633
+
634
+ @txn.should_not_receive( :accepts_html? ).and_return( true )
635
+
636
+ @service.objects.should_receive( :to_xml ).and_return( :xml_object_collection )
637
+
638
+ @service.run( @txn ).should == :xml_object_collection
639
+ end
640
+
641
+ end
642
+
643
+ end
644
+
645
+ # vim: set nosta noet ts=4 sw=4: