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,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: