runo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. data/LICENSE +19 -0
  2. data/README.rdoc +120 -0
  3. data/bin/runo +35 -0
  4. data/lib/_error.rb +14 -0
  5. data/lib/_field.rb +260 -0
  6. data/lib/_i18n.rb +141 -0
  7. data/lib/_parser.rb +243 -0
  8. data/lib/_path.rb +86 -0
  9. data/lib/_storage/_storage.rb +213 -0
  10. data/lib/_storage/file.rb +200 -0
  11. data/lib/_storage/sequel.rb +174 -0
  12. data/lib/_storage/temp.rb +73 -0
  13. data/lib/_widget/action_create.rb +23 -0
  14. data/lib/_widget/action_login.rb +22 -0
  15. data/lib/_widget/action_signup.rb +16 -0
  16. data/lib/_widget/action_update.rb +16 -0
  17. data/lib/_widget/crumb.rb +24 -0
  18. data/lib/_widget/done.rb +16 -0
  19. data/lib/_widget/login.rb +25 -0
  20. data/lib/_widget/me.rb +31 -0
  21. data/lib/_widget/message.rb +51 -0
  22. data/lib/_widget/navi.rb +88 -0
  23. data/lib/_widget/submit.rb +49 -0
  24. data/lib/_widget/view_ym.rb +77 -0
  25. data/lib/_workflow/_workflow.rb +89 -0
  26. data/lib/_workflow/attachment.rb +50 -0
  27. data/lib/_workflow/blog.rb +28 -0
  28. data/lib/_workflow/contact.rb +23 -0
  29. data/lib/_workflow/forum.rb +26 -0
  30. data/lib/_workflow/register.rb +39 -0
  31. data/lib/meta/_meta.rb +20 -0
  32. data/lib/meta/group.rb +19 -0
  33. data/lib/meta/id.rb +59 -0
  34. data/lib/meta/owner.rb +21 -0
  35. data/lib/meta/timestamp.rb +118 -0
  36. data/lib/runo.rb +396 -0
  37. data/lib/scalar/checkbox.rb +68 -0
  38. data/lib/scalar/file.rb +144 -0
  39. data/lib/scalar/img.rb +112 -0
  40. data/lib/scalar/password.rb +58 -0
  41. data/lib/scalar/radio.rb +47 -0
  42. data/lib/scalar/select.rb +47 -0
  43. data/lib/scalar/text.rb +38 -0
  44. data/lib/scalar/textarea.rb +35 -0
  45. data/lib/scalar/textarea_pre.rb +14 -0
  46. data/lib/scalar/textarea_wiki.rb +173 -0
  47. data/lib/set/_set.rb +195 -0
  48. data/lib/set/dynamic.rb +177 -0
  49. data/lib/set/static.rb +102 -0
  50. data/lib/set/static_folder.rb +96 -0
  51. data/locale/en/index.po +242 -0
  52. data/locale/index.pot +243 -0
  53. data/locale/ja/index.po +242 -0
  54. data/locale/lazy_parser.rb +54 -0
  55. data/skel/config.ru +27 -0
  56. data/skel/skin/_users/00000000_frank-avatar.jpg +0 -0
  57. data/skel/skin/_users/00000000_frank-avatar_small.jpg +0 -0
  58. data/skel/skin/_users/00000000_frank.yaml +12 -0
  59. data/skel/skin/_users/00000000_root-avatar.jpg +0 -0
  60. data/skel/skin/_users/00000000_root-avatar_small.jpg +0 -0
  61. data/skel/skin/_users/00000000_root.yaml +11 -0
  62. data/skel/skin/_users/css/users.css +21 -0
  63. data/skel/skin/_users/css/users.less +25 -0
  64. data/skel/skin/_users/done.html +42 -0
  65. data/skel/skin/_users/index.html +46 -0
  66. data/skel/skin/_users/index.yaml +3 -0
  67. data/skel/skin/_users/summary.html +40 -0
  68. data/skel/skin/css/base.css +93 -0
  69. data/skel/skin/css/base.less +139 -0
  70. data/skel/skin/css/coax.css +199 -0
  71. data/skel/skin/css/coax.less +244 -0
  72. data/skel/skin/examples/blog/20091214_0001.yaml +8 -0
  73. data/skel/skin/examples/blog/20100630_0001.yaml +8 -0
  74. data/skel/skin/examples/blog/20100630_0002.yaml +14 -0
  75. data/skel/skin/examples/blog/20100701_0001.yaml +8 -0
  76. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f.jpg +0 -0
  77. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f_small.jpg +0 -0
  78. data/skel/skin/examples/blog/20100701_0002.yaml +19 -0
  79. data/skel/skin/examples/blog/frank/20100701_0001.yaml +10 -0
  80. data/skel/skin/examples/blog/frank/index.yaml +4 -0
  81. data/skel/skin/examples/blog/index.html +51 -0
  82. data/skel/skin/examples/blog/rss.xml +18 -0
  83. data/skel/skin/examples/contact/20100701_0001-file.txt +1 -0
  84. data/skel/skin/examples/contact/20100701_0001.yaml +15 -0
  85. data/skel/skin/examples/contact/20100701_0002.yaml +8 -0
  86. data/skel/skin/examples/contact/20100701_0003.yaml +9 -0
  87. data/skel/skin/examples/contact/index.html +47 -0
  88. data/skel/skin/examples/contact/js/contact.js +13 -0
  89. data/skel/skin/examples/contact/summary.html +54 -0
  90. data/skel/skin/examples/forum/20100701_0001.yaml +41 -0
  91. data/skel/skin/examples/forum/20100701_0002.yaml +25 -0
  92. data/skel/skin/examples/forum/index.html +68 -0
  93. data/skel/skin/examples/forum/summary.html +47 -0
  94. data/skel/skin/examples/index.html +75 -0
  95. data/skel/skin/index.html +41 -0
  96. data/skel/skin/js/base.js +50 -0
  97. data/t/locale/de/index.po +19 -0
  98. data/t/locale/en-GB/index.po +25 -0
  99. data/t/locale/ja/index.po +30 -0
  100. data/t/skin/_users/00000000_test.yaml +3 -0
  101. data/t/skin/_users/index.html +13 -0
  102. data/t/skin/foo/20091120_0001.yaml +7 -0
  103. data/t/skin/foo/bar/20091120_0001.yaml +5 -0
  104. data/t/skin/foo/bar/index.yaml +5 -0
  105. data/t/skin/foo/baz/css/baz.css +1 -0
  106. data/t/skin/foo/css/foo.css +1 -0
  107. data/t/skin/foo/index.html +14 -0
  108. data/t/skin/foo/index.yaml +7 -0
  109. data/t/skin/foo/not_css/foo.css +1 -0
  110. data/t/skin/foo/sub-20100306_0001.yaml +3 -0
  111. data/t/skin/index.yaml +3 -0
  112. data/t/skin/t_attachment/index.html +13 -0
  113. data/t/skin/t_contact/done.html +6 -0
  114. data/t/skin/t_contact/index.html +9 -0
  115. data/t/skin/t_file/index.html +16 -0
  116. data/t/skin/t_img/index.html +14 -0
  117. data/t/skin/t_img/test.jpg +0 -0
  118. data/t/skin/t_select/index.html +9 -0
  119. data/t/skin/t_store/index.html +9 -0
  120. data/t/skin/t_summary/20100326_0001.yaml +3 -0
  121. data/t/skin/t_summary/create.html +9 -0
  122. data/t/skin/t_summary/index.html +9 -0
  123. data/t/skin/t_summary/summary.html +9 -0
  124. data/t/t.rb +27 -0
  125. data/t/test_checkbox.rb +273 -0
  126. data/t/test_field.rb +330 -0
  127. data/t/test_file.rb +900 -0
  128. data/t/test_id.rb +215 -0
  129. data/t/test_img.rb +328 -0
  130. data/t/test_meta.rb +57 -0
  131. data/t/test_parser.rb +1266 -0
  132. data/t/test_password.rb +188 -0
  133. data/t/test_radio.rb +226 -0
  134. data/t/test_role.rb +249 -0
  135. data/t/test_runo.rb +742 -0
  136. data/t/test_runo_call.rb +1286 -0
  137. data/t/test_runo_i18n.rb +318 -0
  138. data/t/test_select.rb +182 -0
  139. data/t/test_set_complex.rb +527 -0
  140. data/t/test_set_dynamic.rb +1504 -0
  141. data/t/test_set_folder.rb +515 -0
  142. data/t/test_set_permit.rb +246 -0
  143. data/t/test_set_static.rb +445 -0
  144. data/t/test_storage.rb +915 -0
  145. data/t/test_text.rb +125 -0
  146. data/t/test_textarea.rb +138 -0
  147. data/t/test_timestamp.rb +473 -0
  148. data/t/test_workflow.rb +367 -0
  149. metadata +345 -0
@@ -0,0 +1,1286 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ require "#{::File.dirname __FILE__}/t"
7
+
8
+ class TC_Runo_Call < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @runo = Runo.new
12
+ end
13
+
14
+ def teardown
15
+ end
16
+
17
+ def test_get
18
+ Runo.client = nil
19
+ res = Rack::MockRequest.new(@runo).get(
20
+ 'http://example.com/foo/'
21
+ )
22
+ assert_match(
23
+ /<html/,
24
+ res.body,
25
+ 'Runo#call() should return the folder.get if the base field is a SD'
26
+ )
27
+ end
28
+
29
+ def test_get_non_exist
30
+ Runo.client = nil
31
+ res = Rack::MockRequest.new(@runo).get(
32
+ 'http://example.com/foo/non/exist/'
33
+ )
34
+ assert_equal(
35
+ 404,
36
+ res.status,
37
+ 'Runo#call() should return 404 if the given path is non-existent'
38
+ )
39
+ end
40
+
41
+ def test_get_partial
42
+ Runo.client = nil
43
+ res = Rack::MockRequest.new(@runo).get(
44
+ 'http://example.com/foo/20091120_0001/name/'
45
+ )
46
+ assert_equal(
47
+ 'FZ',
48
+ res.body,
49
+ 'Runo#call() should return the base.get unless the base field is a SD'
50
+ )
51
+ end
52
+
53
+ def test_get_sub
54
+ Runo.client = nil
55
+ res = Rack::MockRequest.new(@runo).get(
56
+ 'http://example.com/foo/'
57
+ )
58
+ assert_match(
59
+ %r{<li><a>qux</a></li>},
60
+ res.body,
61
+ "Runo#call() should return sets other than 'main' as well"
62
+ )
63
+
64
+ res = Rack::MockRequest.new(@runo).get(
65
+ 'http://example.com/foo/sub/d=2010/'
66
+ )
67
+ assert_match(
68
+ %r{<li><a>qux</a></li>},
69
+ res.body,
70
+ "Runo#call() should pass args for a sd other than 'main' as well"
71
+ )
72
+
73
+ res = Rack::MockRequest.new(@runo).get(
74
+ 'http://example.com/foo/sub/d=2009/'
75
+ )
76
+ assert_no_match(
77
+ %r{<li><a>qux</a></li>},
78
+ res.body,
79
+ "Runo#call() should pass args for a sd other than 'main' as well"
80
+ )
81
+ end
82
+
83
+ def test_get_contact
84
+ Runo.client = nil
85
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.clear
86
+
87
+ res = Rack::MockRequest.new(@runo).get(
88
+ 'http://example.com/1234567890.0123/t_contact/'
89
+ )
90
+ assert_equal(
91
+ 200,
92
+ res.status,
93
+ 'Runo#call to contact by nobody should return status 200'
94
+ )
95
+ assert_equal(
96
+ <<_html,
97
+ <html>
98
+ <head><base href="http:///t_contact/" /><title></title></head>
99
+ <body>
100
+ <h1></h1>
101
+ <form id="form_main" method="post" enctype="multipart/form-data" action="/t_contact/1234567890.0123/update.html">
102
+ <input name="_token" type="hidden" value="#{Runo.token}" />
103
+ <ul id="main" class="runo-contact">
104
+ <li><a><span class="text"><input type="text" name="_001-name" value="foo" size="32" /></span></a>: <span class="text"><input type="text" name="_001-comment" value="bar!" size="64" /></span></li>
105
+ </ul>
106
+ <div class="submit">
107
+ <input name=".status-public" type="submit" value="create" />
108
+ </div>
109
+ </form>
110
+ </body>
111
+ </html>
112
+ _html
113
+ res.body,
114
+ 'Runo#call to contact by nobody should return :create'
115
+ )
116
+ end
117
+
118
+ def test_get_contact_forbidden
119
+ Runo.client = nil
120
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.build(
121
+ '20100425_1234' => {'name' => 'cz', 'comment' => 'howdy.'}
122
+ )
123
+
124
+ res = Rack::MockRequest.new(@runo).get(
125
+ 'http://example.com/t_contact/20100425/1234/index.html'
126
+ )
127
+ assert_no_match(
128
+ /howdy/,
129
+ res.body,
130
+ 'getting an contact by nobody should not return any contents'
131
+ )
132
+ res = Rack::MockRequest.new(@runo).get(
133
+ 'http://example.com/t_contact/20100425_1234/'
134
+ )
135
+ assert_no_match(
136
+ /howdy/,
137
+ res.body,
138
+ 'peeking an contact by nobody should not return any contents'
139
+ )
140
+ res = Rack::MockRequest.new(@runo).get(
141
+ 'http://example.com/t_contact/20100425_1234/comment/'
142
+ )
143
+ assert_no_match(
144
+ /howdy/,
145
+ res.body,
146
+ 'peeking an contact by nobody should not return any contents'
147
+ )
148
+
149
+ res = Rack::MockRequest.new(@runo).get(
150
+ 'http://example.com/t_contact/20100425/1234/done.html'
151
+ )
152
+ assert_no_match(
153
+ /howdy/,
154
+ res.body,
155
+ 'getting an contact by nobody should not return any contents'
156
+ )
157
+ res = Rack::MockRequest.new(@runo).get(
158
+ 'http://example.com/t_contact/20100425_1234/done.html'
159
+ )
160
+ assert_no_match(
161
+ /howdy/,
162
+ res.body,
163
+ 'peeking an contact by nobody should not return any contents'
164
+ )
165
+ res = Rack::MockRequest.new(@runo).get(
166
+ 'http://example.com/t_contact/20100425_1234/comment/done.html'
167
+ )
168
+ assert_no_match(
169
+ /howdy/,
170
+ res.body,
171
+ 'peeking an contact by nobody should not return any contents'
172
+ )
173
+
174
+ res = Rack::MockRequest.new(@runo).get(
175
+ 'http://example.com/t_contact/20100425/1234/preview_create.html'
176
+ )
177
+ assert_no_match(
178
+ /howdy/,
179
+ res.body,
180
+ 'getting an contact by nobody should not return any contents'
181
+ )
182
+ res = Rack::MockRequest.new(@runo).get(
183
+ 'http://example.com/t_contact/20100425_1234/preview_create.html'
184
+ )
185
+ assert_no_match(
186
+ /howdy/,
187
+ res.body,
188
+ 'peeking an contact by nobody should not return any contents'
189
+ )
190
+ res = Rack::MockRequest.new(@runo).get(
191
+ 'http://example.com/t_contact/20100425_1234/comment/preview_create.html'
192
+ )
193
+ assert_no_match(
194
+ /howdy/,
195
+ res.body,
196
+ 'peeking an contact by nobody should not return any contents'
197
+ )
198
+ end
199
+
200
+ def test_get_summary
201
+ Runo.client = nil
202
+ res = Rack::MockRequest.new(@runo).get(
203
+ 'http://example.com/t_summary/p=1/'
204
+ )
205
+ assert_equal(
206
+ <<'_html',
207
+ <html>
208
+ <head><base href="http:///t_summary/" /><title>summary</title></head>
209
+ <body>
210
+ <h1>summary</h1>
211
+ <table id="main" class="runo-blog">
212
+ <tr><td><a href="/t_summary/20100326/1/read_detail.html">frank</a></td><td>hi.</td></tr>
213
+ </table>
214
+ </body>
215
+ </html>
216
+ _html
217
+ res.body,
218
+ 'Runo#call() should use [:tmpl][:summary] when available and appropriate'
219
+ )
220
+
221
+ res = Rack::MockRequest.new(@runo).get(
222
+ 'http://example.com/t_summary/p=1/read_detail.html'
223
+ )
224
+ assert_equal(
225
+ <<'_html',
226
+ <html>
227
+ <head><base href="http:///t_summary/" /><title>index</title></head>
228
+ <body>
229
+ <h1>index</h1>
230
+ <ul id="main" class="runo-blog">
231
+ <li><a>frank</a>: hi.</li>
232
+ </ul>
233
+ </body>
234
+ </html>
235
+ _html
236
+ res.body,
237
+ 'Runo#call() should use [:tmpl] when the action is :read -> :detail'
238
+ )
239
+
240
+ Runo.client = 'root'
241
+ res = Rack::MockRequest.new(@runo).get(
242
+ "http://example.com/t_summary/p=1/update.html"
243
+ )
244
+ tid = res.body[%r{/(#{Runo::REX::TID})/}, 1]
245
+ assert_equal(
246
+ <<"_html",
247
+ <html>
248
+ <head><base href="http:///t_summary/" /><title>index</title></head>
249
+ <body>
250
+ <h1>index</h1>
251
+ <form id="form_main" method="post" enctype="multipart/form-data" action="/t_summary/#{tid}/update.html">
252
+ <input name="_token" type="hidden" value="#{Runo.token}" />
253
+ <ul id="main" class="runo-blog">
254
+ <li><a><span class="text"><input type="text" name="20100326_0001-name" value="frank" size="32" /></span></a>: <span class="text"><input type="text" name="20100326_0001-comment" value="hi." size="64" /></span></li>
255
+ </ul>
256
+ <div class="submit">
257
+ <input name=".status-public" type="submit" value="update" />
258
+ <input name=".action-preview_delete" type="submit" value="delete..." />
259
+ </div>
260
+ </form>
261
+ </body>
262
+ </html>
263
+ _html
264
+ res.body,
265
+ 'Runo#call() should use [:tmpl] unless the action is :read'
266
+ )
267
+ end
268
+
269
+ def test_get_static
270
+ Runo.client = 'root'
271
+ res = Rack::MockRequest.new(@runo).get(
272
+ 'http://example.com/foo/css/foo.css'
273
+ )
274
+ assert_equal(
275
+ 200,
276
+ res.status,
277
+ 'Runo#call should return a static file if the given path is under css/, js/, etc.'
278
+ )
279
+ assert_equal(
280
+ 'text/css',
281
+ res.headers['Content-Type'],
282
+ 'Runo#call should return a static file if the given path is under css/, js/, etc.'
283
+ )
284
+ assert_equal(
285
+ "#foo {bar: baz;}\n",
286
+ res.body,
287
+ 'Runo#call should return a static file if the given path is under css/, js/, etc.'
288
+ )
289
+ end
290
+
291
+ def test_get_static_not_css
292
+ Runo.client = 'root'
293
+ res = Rack::MockRequest.new(@runo).get(
294
+ 'http://example.com/foo/not_css/foo.css'
295
+ )
296
+ assert_not_equal(
297
+ "#foo {bar: baz;}\n",
298
+ res.body,
299
+ 'Runo#call should not return a static file if the step is not exactly css/, js/, etc.'
300
+ )
301
+ end
302
+
303
+ def test_get_static_non_exist
304
+ Runo.client = 'root'
305
+ res = Rack::MockRequest.new(@runo).get(
306
+ 'http://example.com/foo/css/non-exist.css'
307
+ )
308
+ assert_equal(
309
+ 404,
310
+ res.status,
311
+ 'Runo#call should return 404 if the static file is not found'
312
+ )
313
+ end
314
+
315
+ def test_get_static_ambiguous
316
+ Runo.client = 'root'
317
+ res = Rack::MockRequest.new(@runo).get(
318
+ 'http://example.com/foo/css/0123456789.1234/_001/img/bar.jpg'
319
+ )
320
+ assert_equal(
321
+ 'Not Found', # from Runo#call
322
+ res.body,
323
+ 'Runo#call should not look for a static file if the path includes a tid'
324
+ )
325
+
326
+ res = Rack::MockRequest.new(@runo).get(
327
+ 'http://example.com/foo/css/20100613_1234/img/bar.jpg'
328
+ )
329
+ assert_equal(
330
+ 'Not Found', # from Runo#call
331
+ res.body,
332
+ 'Runo#call should not look for a static file if the path includes an id'
333
+ )
334
+
335
+ res = Rack::MockRequest.new(@runo).get(
336
+ 'http://example.com/foo/css/0123456789.1234/20100613_1234/img/bar.jpg'
337
+ )
338
+ assert_equal(
339
+ 'Not Found', # from Runo#call
340
+ res.body,
341
+ 'Runo#call should not look for a static file if the path includes an id'
342
+ )
343
+
344
+ res = Rack::MockRequest.new(@runo).get(
345
+ 'http://example.com/foo/css/20100613_1234/bar/id=img/'
346
+ )
347
+ assert_equal(
348
+ 'Not Found', # from Runo#call
349
+ res.body,
350
+ 'Runo#call should not look for a static file if the path includes an id'
351
+ )
352
+ end
353
+
354
+ def test_get_static_forbidden
355
+ Runo.client = 'root'
356
+ res = Rack::MockRequest.new(@runo).get(
357
+ 'http://example.com/_users/00000000_test.yaml'
358
+ )
359
+ assert_no_match(
360
+ /^password/,
361
+ res.body,
362
+ 'Runo#call should not return meta files nor raw data'
363
+ )
364
+
365
+ res = Rack::MockRequest.new(@runo).get(
366
+ 'http://example.com/_users/css/../00000000_test.yaml'
367
+ )
368
+ assert_no_match(
369
+ /password/,
370
+ res.body,
371
+ 'Runo#call should not allow bad paths'
372
+ )
373
+ end
374
+
375
+ def test_get_static_cascade
376
+ Runo.client = 'root'
377
+
378
+ res = Rack::MockRequest.new(@runo).get(
379
+ 'http://example.com/foo/bar/css/foo.css'
380
+ )
381
+ assert_equal(
382
+ "#foo {bar: baz;}\n",
383
+ res.body,
384
+ 'Runo#call should look the acnestor dirs for the static directory'
385
+ )
386
+
387
+ res = Rack::MockRequest.new(@runo).get(
388
+ 'http://example.com/foo/bar/css/non_exist.css'
389
+ )
390
+ assert_equal(
391
+ 404,
392
+ res.status,
393
+ 'Runo#call should return 404 if the file is not found in the nearest static dir'
394
+ )
395
+
396
+ res = Rack::MockRequest.new(@runo).get(
397
+ 'http://example.com/foo/baz/css/foo.css'
398
+ )
399
+ assert_equal(
400
+ 404,
401
+ res.status,
402
+ 'Runo#call should not look the ancestor dirs if the file is not found in the nearest dir'
403
+ )
404
+
405
+ res = Rack::MockRequest.new(@runo).get(
406
+ 'http://example.com/foo/bar/js/non_exist.css'
407
+ )
408
+ assert_equal(
409
+ 404,
410
+ res.status,
411
+ 'Runo#call should return 404 if the static dir is not found in any ancestor dirs'
412
+ )
413
+ end
414
+
415
+ def test_post_simple_create
416
+ Runo.client = 'root'
417
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
418
+
419
+ res = Rack::MockRequest.new(@runo).post(
420
+ 'http://example.com/t_store/main/update.html',
421
+ {
422
+ :input => "_1-name=fz&_1-comment=hi.&.status-public=create&_token=#{Runo.token}"
423
+ }
424
+ )
425
+ assert_equal(
426
+ 303,
427
+ res.status,
428
+ 'Runo#call with post method should return status 303'
429
+ )
430
+ assert_match(
431
+ Runo::REX::PATH_ID,
432
+ res.headers['Location'],
433
+ 'Runo#call with post method should return a proper location'
434
+ )
435
+
436
+ res.headers['Location'] =~ Runo::REX::PATH_ID
437
+ new_id = sprintf('%.8d_%.4d', $1, $2)
438
+
439
+ val = Runo::Set::Static::Folder.root.item('t_store', 'main', new_id).val
440
+ assert_instance_of(
441
+ ::Hash,
442
+ val,
443
+ 'Runo#call with post method should store the item in the storage'
444
+ )
445
+ val.delete '_timestamp'
446
+ assert_equal(
447
+ {'_owner' => 'root', 'name' => 'fz', 'comment' => 'hi.'},
448
+ val,
449
+ 'Runo#call with post method should store the item in the storage'
450
+ )
451
+ end
452
+
453
+ def test_post_invalid_create
454
+ Runo.client = 'root'
455
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
456
+
457
+ res = Rack::MockRequest.new(@runo).post(
458
+ "http://example.com/t_store/main/update.html",
459
+ {
460
+ :input => "_1-name=tooooooooooooloooooooooooooooooooooooooooong&_1-comment=hi.&.status-public=create&_token=#{Runo.token}"
461
+ }
462
+ )
463
+ tid = res.body[%r{/(#{Runo::REX::TID})/}, 1]
464
+
465
+ assert_equal(
466
+ 422,
467
+ res.status,
468
+ 'Runo#post with an invalid new item should be an error'
469
+ )
470
+ assert_instance_of(
471
+ Runo::Set::Dynamic,
472
+ Runo.transaction[tid],
473
+ 'the suspended SD should be kept in Runo.transaction'
474
+ )
475
+ end
476
+
477
+ def test_post_empty_create
478
+ Runo.client = 'root'
479
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
480
+
481
+ res = Rack::MockRequest.new(@runo).post(
482
+ 'http://example.com/t_store/main/update.html',
483
+ {
484
+ :input => "_1-name=&_1-comment=&.status-public=create&_token=#{Runo.token}"
485
+ }
486
+ )
487
+ assert_equal(
488
+ 422,
489
+ res.status,
490
+ 'Runo#post with an empty new item should be an error'
491
+ )
492
+ end
493
+
494
+ def test_post_empty_update
495
+ Runo.client = 'root'
496
+
497
+ res = Rack::MockRequest.new(@runo).post(
498
+ 'http://example.com/t_store/main/update.html',
499
+ {
500
+ :input => "_1-name=don&_1-comment=brown.&.status-public=create&_token=#{Runo.token}"
501
+ }
502
+ )
503
+ res.headers['Location'] =~ Runo::REX::PATH_ID
504
+ new_id = sprintf('%.8d_%.4d', $1, $2)
505
+
506
+ res = Rack::MockRequest.new(@runo).post(
507
+ 'http://example.com/t_store/main/update.html',
508
+ {
509
+ :input => "#{new_id}-name=don&.status-public=update&_token=#{Runo.token}"
510
+ }
511
+ )
512
+ assert_equal(
513
+ 303,
514
+ res.status,
515
+ 'Runo#post without any update on the item should not raise an error'
516
+ )
517
+
518
+ res = Rack::MockRequest.new(@runo).get(
519
+ res.headers['Location']
520
+ )
521
+ assert_no_match(
522
+ /message/,
523
+ res.body,
524
+ 'Runo#post without any update on the item should not set any messages'
525
+ )
526
+ end
527
+
528
+ def test_post_back_and_forth
529
+ Runo.client = 'root'
530
+
531
+ # post from a form without status
532
+ res = Rack::MockRequest.new(@runo).post(
533
+ 'http://example.com/t_store/main/1234567890.9999/update.html',
534
+ {
535
+ :input => "_1-comment=brown."
536
+ }
537
+ )
538
+
539
+ # back to the form, post again with status
540
+ res = Rack::MockRequest.new(@runo).post(
541
+ 'http://example.com/t_store/main/1234567890.9999/update.html',
542
+ {
543
+ :input => "_1-name=don&_1-comment=brown.&.status-public=create&_token=#{Runo.token}"
544
+ }
545
+ )
546
+ assert_equal(
547
+ 303,
548
+ res.status,
549
+ 'Runo#post twice from the same form should work if the previous post is without status'
550
+ )
551
+
552
+ # back to the form one more time, post again with status
553
+ res = Rack::MockRequest.new(@runo).post(
554
+ 'http://example.com/t_store/main/1234567890.9999/update.html',
555
+ {
556
+ :input => "_1-name=roy&_1-comment=brown.&.status-public=create&_token=#{Runo.token}"
557
+ }
558
+ )
559
+ assert_equal(
560
+ 422,
561
+ res.status,
562
+ 'Runo#post twice from the same form should not work if the previous post is with status'
563
+ )
564
+ assert_equal(
565
+ 'transaction expired',
566
+ res.body,
567
+ 'Runo#post twice from the same form should not work if the previous post is with status'
568
+ )
569
+ end
570
+
571
+ def test_post_with_invalid_token
572
+ Runo.client = 'root'
573
+ res = Rack::MockRequest.new(@runo).post(
574
+ 'http://example.com/t_store/main/update.html',
575
+ {
576
+ :input => "_1-name=fz&_1-comment=hi.&.status-public=create&_token=invalid"
577
+ }
578
+ )
579
+ assert_equal(
580
+ 403,
581
+ res.status,
582
+ 'Runo#call without a valid token should return status 403'
583
+ )
584
+ assert_equal(
585
+ 'invalid token',
586
+ res.body,
587
+ 'Runo#call without a valid token should return status 403'
588
+ )
589
+ end
590
+
591
+ def test_post_with_attachment
592
+ Runo.client = 'root'
593
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main').storage.clear
594
+
595
+ # post an attachment
596
+ res = Rack::MockRequest.new(@runo).post(
597
+ 'http://example.com/t_attachment/main/update.html',
598
+ {
599
+ :input => "_012-files-_1-file=wow.jpg&_012-files-_1.action-create=create&_token=#{Runo.token}"
600
+ }
601
+ )
602
+ assert_match(
603
+ 'update.html',
604
+ res.headers['Location'],
605
+ 'Runo#call without the root status should always return :update'
606
+ )
607
+ assert_equal(
608
+ {},
609
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main').val,
610
+ 'Runo#call without the root status should not update the persistent storage'
611
+ )
612
+
613
+ tid = res.headers['Location'][Runo::REX::TID]
614
+ assert_instance_of(
615
+ Runo::Set::Dynamic,
616
+ Runo.transaction[tid],
617
+ 'the suspended SD should be kept in Runo.transaction'
618
+ )
619
+
620
+ # post the item
621
+ res = Rack::MockRequest.new(@runo).post(
622
+ "http://example.com/#{tid}/update.html",
623
+ {
624
+ :input => "_012-comment=hello.&.status-public=create&_token=#{Runo.token}"
625
+ }
626
+ )
627
+ assert_no_match(
628
+ /update\.html/,
629
+ res.headers['Location'],
630
+ 'Runo#call with the root status should commit the transaction'
631
+ )
632
+ assert_not_equal(
633
+ {},
634
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main').val,
635
+ 'Runo#call with the root status should update the persistent storage'
636
+ )
637
+
638
+ res.headers['Location'] =~ Runo::REX::PATH_ID
639
+ new_id = sprintf('%.8d_%.4d', $1, $2)
640
+ new_item = Runo::Set::Static::Folder.root.item('t_attachment', 'main', new_id)
641
+ assert_not_equal(
642
+ {},
643
+ new_item.val,
644
+ 'Runo#call with the root status should commit all the pending items'
645
+ )
646
+ assert_equal(
647
+ 'hello.',
648
+ new_item.val['comment'],
649
+ 'Runo#call with the root status should commit the root items'
650
+ )
651
+ assert_equal(
652
+ {'file' => 'wow.jpg'},
653
+ new_item.val['files'].values.first,
654
+ 'Runo#call with the root status should commit the descendant items'
655
+ )
656
+
657
+ # post a reply
658
+ res = Rack::MockRequest.new(@runo).post(
659
+ "http://example.com/t_attachment/main/#{new_id}/replies/update.html",
660
+ {
661
+ :input => "_001-reply=wow.&.status-public=create&_token=#{Runo.token}"
662
+ }
663
+ )
664
+ assert_equal(303, res.status)
665
+ assert_match(
666
+ %r{/#{Runo::REX::TID}/t_attachment/#{new_id}/replies/read_detail.html},
667
+ res.headers['Location'],
668
+ 'Runo#call with a sub-app status should commit the root item'
669
+ )
670
+ assert_not_equal(
671
+ {},
672
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main', new_id, 'replies').val,
673
+ 'Runo#call with a sub-app status should update the persistent storage'
674
+ )
675
+ end
676
+
677
+ def test_post_only_attachment
678
+ Runo.client = 'root'
679
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main').storage.clear
680
+
681
+ # post an item
682
+ res = Rack::MockRequest.new(@runo).post(
683
+ "http://example.com/t_attachment/main/update.html",
684
+ {
685
+ :input => "_012-comment=abc&.status-public=create"
686
+ }
687
+ )
688
+ res.headers['Location'] =~ Runo::REX::PATH_ID
689
+ new_id = sprintf('%.8d_%.4d', $1, $2)
690
+
691
+ # post an attachment
692
+ tid = '1234567890.1234'
693
+ res = Rack::MockRequest.new(@runo).post(
694
+ "http://example.com/#{tid}/t_attachment/update.html",
695
+ {
696
+ :input => "#{new_id}-files-_1-file=boo.jpg&#{new_id}-files-_1.action-create=create&_token=#{Runo.token}"
697
+ }
698
+ )
699
+ attachment_id = Runo.transaction[tid].item(new_id, 'files').val.keys.first
700
+ res = Rack::MockRequest.new(@runo).post(
701
+ "http://example.com/#{tid}/update.html",
702
+ {
703
+ :input => "#{new_id}-files-#{attachment_id}-file=boo.jpg&#{new_id}-files-_1-file=&.status-public=create&_token=#{Runo.token}"
704
+ }
705
+ )
706
+
707
+ assert_not_nil(
708
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main', new_id).val['files'],
709
+ 'Runo#call should treat the post with only an attachement nicely'
710
+ )
711
+ end
712
+
713
+ def test_post_with_invalid_attachment
714
+ Runo.client = 'root'
715
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main').storage.clear
716
+
717
+ # post an invalid empty attachment
718
+ res = Rack::MockRequest.new(@runo).post(
719
+ 'http://example.com/t_attachment/main/update.html',
720
+ {
721
+ :input => "_012-files-_1-file=&_012-files-_1.action-create=create&_token=#{Runo.token}"
722
+ }
723
+ )
724
+ tid = res.headers['Location'][Runo::REX::TID]
725
+
726
+ # post the root item
727
+ res = Rack::MockRequest.new(@runo).post(
728
+ "http://example.com/#{tid}/update.html",
729
+ {
730
+ :input => "_012-comment=hello.&.status-public=create&_token=#{Runo.token}"
731
+ }
732
+ )
733
+ assert_equal(
734
+ 303,
735
+ res.status,
736
+ 'Runo#call with the root status should ignore the invalid empty attachment'
737
+ )
738
+ assert_not_equal(
739
+ {},
740
+ Runo::Set::Static::Folder.root.item('t_attachment', 'main').val,
741
+ 'Runo#call with the root status should ignore the invalid empty attachment'
742
+ )
743
+
744
+ # post an invalid non-empty attachment
745
+ res = Rack::MockRequest.new(@runo).post(
746
+ 'http://example.com/t_attachment/main/update.html',
747
+ {
748
+ :input => "_012-files-_1-file=tooloooooooooooong&_012-files-_1.action-create=create&_token=#{Runo.token}"
749
+ }
750
+ )
751
+ tid = res.headers['Location'][Runo::REX::TID]
752
+
753
+ # post the root item
754
+ res = Rack::MockRequest.new(@runo).post(
755
+ "http://example.com/#{tid}/update.html",
756
+ {
757
+ :input => "_012-comment=hello.&.status-public=create&_token=#{Runo.token}"
758
+ }
759
+ )
760
+ assert_equal(
761
+ 422,
762
+ res.status,
763
+ 'Runo#call with the root status should not ignore the invalid non-empty attachment'
764
+ )
765
+ end
766
+
767
+ def test_post_preview_update
768
+ Runo.client = 'root'
769
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
770
+ base_uri = ''
771
+
772
+ res = Rack::MockRequest.new(@runo).post(
773
+ "http://#{base_uri}/t_store/main/update.html",
774
+ {
775
+ :input => ".action-preview_update=submit&_1-name=fz&_1-comment=howdy.&.status-public=create"
776
+ }
777
+ )
778
+ tid = res.headers['Location'][Runo::REX::TID]
779
+
780
+ assert_equal(
781
+ 303,
782
+ res.status,
783
+ 'Runo#call with :preview action should return status 303 upon success'
784
+ )
785
+ assert_equal(
786
+ "http://#{base_uri}/#{tid}/id=_1/preview_update.html",
787
+ res.headers['Location'],
788
+ 'Runo#call with :preview action should return a proper location'
789
+ )
790
+ assert_instance_of(
791
+ Runo::Set::Dynamic,
792
+ Runo.transaction[tid],
793
+ 'the suspended SD should be kept in Runo.transaction'
794
+ )
795
+
796
+ res = Rack::MockRequest.new(@runo).get(
797
+ "http://#{base_uri}/#{tid}/id=_1/preview_update.html"
798
+ )
799
+ assert_equal(
800
+ <<"_html",
801
+ <html>
802
+ <head><base href="http://#{base_uri}/t_store/" /><title></title></head>
803
+ <body>
804
+ <h1></h1>
805
+ <ul class="message notice">
806
+ <li>please confirm.</li>
807
+ </ul>
808
+ <form id="form_main" method="post" enctype="multipart/form-data" action="/#{tid}/update.html">
809
+ <input name="_token" type="hidden" value="#{Runo.token}" />
810
+ <ul id="main" class="runo-blog">
811
+ <li><a>fz</a>: howdy.<input type="hidden" name="_1.action" value="create" /></li>
812
+ </ul>
813
+ <div class="submit">
814
+ <input name=".status-public" type="submit" value="create" />
815
+ </div>
816
+ </form>
817
+ </body>
818
+ </html>
819
+ _html
820
+ res.body,
821
+ 'Runo#call with :preview action should set a proper transaction upon success'
822
+ )
823
+
824
+ res = Rack::MockRequest.new(@runo).post(
825
+ "http://example.com/#{tid}/update.html",
826
+ {
827
+ :input => "_1.action=create&.status-public=create&_token=#{Runo.token}"
828
+ }
829
+ )
830
+ assert_equal(
831
+ 303,
832
+ res.status,
833
+ 'Runo#call with post method should return status 303'
834
+ )
835
+ assert_match(
836
+ Runo::REX::PATH_ID,
837
+ res.headers['Location'],
838
+ 'Runo#call with post method should return a proper location'
839
+ )
840
+
841
+ res.headers['Location'] =~ Runo::REX::PATH_ID
842
+ new_id = sprintf('%.8d_%.4d', $1, $2)
843
+
844
+ val = Runo::Set::Static::Folder.root.item('t_store', 'main', new_id).val
845
+ assert_instance_of(
846
+ ::Hash,
847
+ val,
848
+ 'Runo#call with post method should store the item in the storage'
849
+ )
850
+ val.delete '_timestamp'
851
+ assert_equal(
852
+ {'_owner' => 'root', 'name' => 'fz', 'comment' => 'howdy.'},
853
+ val,
854
+ 'Runo#call with post method should store the item in the storage'
855
+ )
856
+ end
857
+
858
+ def test_post_preview_invalid
859
+ Runo.client = 'root'
860
+
861
+ res = Rack::MockRequest.new(@runo).post(
862
+ 'http://example.com/t_store/main/update.html',
863
+ {
864
+ :input => ".action-preview_update=submit&_1-name=verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrylong&_1-comment=howdy.&.status-public=create"
865
+ }
866
+ )
867
+ assert_equal(
868
+ 422,
869
+ res.status,
870
+ 'Runo#call with :preview action & malformed input should return status 422'
871
+ )
872
+ assert_match(
873
+ /malformed input\./,
874
+ res.body,
875
+ 'Runo#call with :preview action & malformed input should return :update'
876
+ )
877
+
878
+ tid = res.body[%r{/(#{Runo::REX::TID})/}, 1]
879
+ assert_instance_of(
880
+ Runo::Set::Dynamic,
881
+ Runo.transaction[tid],
882
+ 'the suspended SD should be kept in Runo.transaction'
883
+ )
884
+ end
885
+
886
+ def test_post_contact
887
+ Runo.client = nil
888
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.clear
889
+
890
+ res = Rack::MockRequest.new(@runo).post(
891
+ 'http://example.com/t_contact/main/update.html',
892
+ {
893
+ :input => "_1-name=fz&_1-comment=hi.&.status-public=create&_token=#{Runo.token}"
894
+ }
895
+ )
896
+ assert_equal(
897
+ 303,
898
+ res.status,
899
+ 'Runo#call with post method should return status 303'
900
+ )
901
+ assert_no_match(
902
+ Runo::REX::PATH_ID,
903
+ res.headers['Location'],
904
+ 'Runo#call should not tell the item location when the workflow is contact'
905
+ )
906
+
907
+ res = Rack::MockRequest.new(@runo).get(
908
+ res.headers['Location'],
909
+ {}
910
+ )
911
+ assert_match(
912
+ /thank you!/,
913
+ res.body,
914
+ 'Runo#call should refer to (action).html if available'
915
+ )
916
+ assert_no_match(
917
+ /message/,
918
+ res.body,
919
+ 'Runo#call should not include messages for action :done'
920
+ )
921
+ assert_no_match(
922
+ /login/,
923
+ res.body,
924
+ 'Runo#call should always allow action :done'
925
+ )
926
+ end
927
+
928
+ def test_post_contact_forbidden
929
+ Runo.client = nil
930
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.build(
931
+ '20100425_1234' => {'_owner' => 'nobody', 'name' => 'cz', 'comment' => 'howdy.'}
932
+ )
933
+
934
+ res = Rack::MockRequest.new(@runo).post(
935
+ 'http://example.com/t_contact/main/update.html',
936
+ {
937
+ :input => "20100425_1234-comment=modified&20100425_1234.action=create&.status-public=create"
938
+ }
939
+ )
940
+ assert_equal(
941
+ 403,
942
+ res.status,
943
+ 'Runo#call should not allow nobody to update an existing contact'
944
+ )
945
+ assert_equal(
946
+ {'20100425_1234' => {'_owner' => 'nobody', 'name' => 'cz', 'comment' => 'howdy.'}},
947
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.val,
948
+ 'Runo#call should not allow nobody to update an existing contact'
949
+ )
950
+
951
+ res = Rack::MockRequest.new(@runo).post(
952
+ 'http://example.com/t_contact/main/create.html',
953
+ {
954
+ :input => "20100425_1234-comment=modified&20100425_1234.action=create&.status-public=create"
955
+ }
956
+ )
957
+ assert_equal(
958
+ 403,
959
+ res.status,
960
+ 'Runo#call should not allow nobody to update an existing contact'
961
+ )
962
+ assert_equal(
963
+ {'20100425_1234' => {'_owner' => 'nobody', 'name' => 'cz', 'comment' => 'howdy.'}},
964
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.val,
965
+ 'Runo#call should not allow nobody to update an existing contact'
966
+ )
967
+
968
+ res = Rack::MockRequest.new(@runo).post(
969
+ 'http://example.com/t_contact/main/20100425_1234/create.html',
970
+ {
971
+ :input => "comment=modified&.action=create&.status-public=create"
972
+ }
973
+ )
974
+ assert_equal(
975
+ 403,
976
+ res.status,
977
+ 'Runo#call should not allow nobody to update an existing contact'
978
+ )
979
+ assert_equal(
980
+ {'20100425_1234' => {'_owner' => 'nobody', 'name' => 'cz', 'comment' => 'howdy.'}},
981
+ Runo::Set::Static::Folder.root.item('t_contact', 'main').storage.val,
982
+ 'Runo#call should not allow nobody to update an existing contact'
983
+ )
984
+ end
985
+
986
+ def test_post_wrong_action
987
+ Runo.client = nil
988
+ res = Rack::MockRequest.new(@runo).post(
989
+ 'http://example.com/t_store/main/read.html',
990
+ {
991
+ :input => "_1-name=fz&_1-comment=hi.&.status-public=create"
992
+ }
993
+ )
994
+ assert_equal(
995
+ 403,
996
+ res.status,
997
+ 'post with an action other than :update should be regarded as :update'
998
+ )
999
+
1000
+ Runo.client = 'root'
1001
+ res = Rack::MockRequest.new(@runo).post(
1002
+ 'http://example.com/t_store/main/read.html',
1003
+ {
1004
+ :input => "_1-name=fz&_1-comment=hi.&.status-public=create&_token=#{Runo.token}"
1005
+ }
1006
+ )
1007
+ assert_equal(
1008
+ 303,
1009
+ res.status,
1010
+ 'post with an action other than :update should be regarded as :update'
1011
+ )
1012
+ end
1013
+
1014
+ def test_post_login
1015
+ Runo.client = nil
1016
+ res = Rack::MockRequest.new(@runo).post(
1017
+ "http://example.com/foo/20100222/1/login.html",
1018
+ {
1019
+ :input => "id=test&pw=test&dest_action=update"
1020
+ }
1021
+ )
1022
+ assert_equal(
1023
+ 'test',
1024
+ Runo.client,
1025
+ 'Runo#call with :login action should set Runo.client upon success'
1026
+ )
1027
+ assert_equal(
1028
+ 303,
1029
+ res.status,
1030
+ 'Runo#call with :login action should return status 303'
1031
+ )
1032
+ assert_match(
1033
+ %r{/foo/20100222/1/update.html},
1034
+ res.headers['Location'],
1035
+ 'Runo#call with :login action should return a proper location'
1036
+ )
1037
+ end
1038
+
1039
+ def test_post_login_with_wrong_pw
1040
+ Runo.client = nil
1041
+ res = Rack::MockRequest.new(@runo).post(
1042
+ "http://example.com/foo/20100222/1/login.html",
1043
+ {
1044
+ :input => "id=test&pw=wrong&dest_action=update"
1045
+ }
1046
+ )
1047
+ assert_equal(
1048
+ 'nobody',
1049
+ Runo.client,
1050
+ 'Runo#call with :login action should not set Runo.client upon failure'
1051
+ )
1052
+ assert_equal(
1053
+ 422,
1054
+ res.status,
1055
+ 'Runo#call with :login action should return status 422 upon failure'
1056
+ )
1057
+ end
1058
+
1059
+ def test_post_logout
1060
+ Runo.client = 'frank'
1061
+ res = Rack::MockRequest.new(@runo).post(
1062
+ "http://example.com/foo/20100222/1/logout.html?_token=#{Runo.token}",
1063
+ {}
1064
+ )
1065
+ assert_equal(
1066
+ 'nobody',
1067
+ Runo.client,
1068
+ 'Runo#call with :logout action should unset Runo.client'
1069
+ )
1070
+ assert_equal(
1071
+ 303,
1072
+ res.status,
1073
+ 'Runo#call with :logout action should return status 303'
1074
+ )
1075
+ assert_match(
1076
+ %r{/foo/20100222/1/index.html},
1077
+ res.headers['Location'],
1078
+ 'Runo#call with :logout action should return a proper location'
1079
+ )
1080
+ end
1081
+
1082
+ def test_post_logout_with_invalid_token
1083
+ Runo.client = 'frank'
1084
+ res = Rack::MockRequest.new(@runo).post(
1085
+ "http://example.com/foo/20100222/1/logout.html?_token=invalid",
1086
+ {}
1087
+ )
1088
+ assert_equal(
1089
+ 403,
1090
+ res.status,
1091
+ 'Runo#call without a valid token should return status 403'
1092
+ )
1093
+ assert_equal(
1094
+ 'invalid token',
1095
+ res.body,
1096
+ 'Runo#call without a valid token should return status 403'
1097
+ )
1098
+ assert_equal(
1099
+ 'frank',
1100
+ Runo.client,
1101
+ 'Runo#call without a valid token should return status 403'
1102
+ )
1103
+ end
1104
+
1105
+ def test_get_logout
1106
+ Runo.client = 'frank'
1107
+ res = Rack::MockRequest.new(@runo).get(
1108
+ "http://example.com/foo/20100222/1/logout.html?_token=#{Runo.token}",
1109
+ {}
1110
+ )
1111
+ assert_equal(
1112
+ 'nobody',
1113
+ Runo.client,
1114
+ 'Runo#call with :logout action should work via both get and post'
1115
+ )
1116
+ assert_match(
1117
+ %r{/foo/20100222/1/index.html},
1118
+ res.headers['Location'],
1119
+ 'Runo#call with :logout action should work via both get and post'
1120
+ )
1121
+ end
1122
+
1123
+ def test_message_notice
1124
+ Runo.client = 'root'
1125
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
1126
+
1127
+ res = Rack::MockRequest.new(@runo).post(
1128
+ 'http://example.com/t_store/main/update.html',
1129
+ {
1130
+ :input => "_2-name=fz&_2-comment=hi.&.status-public=create&_token=#{Runo.token}"
1131
+ }
1132
+ )
1133
+ assert_match(
1134
+ %r{#{Runo::REX::TID}/t_store/},
1135
+ res.headers['Location'],
1136
+ 'Runo#call should return both the base path and tid at :done'
1137
+ )
1138
+
1139
+ tid = res.headers['Location'][Runo::REX::TID]
1140
+ new_id = res.headers['Location'][Runo::REX::PATH_ID]
1141
+
1142
+ res = Rack::MockRequest.new(@runo).get(
1143
+ res.headers['Location']
1144
+ )
1145
+ assert_match(
1146
+ /created 1 entry\./,
1147
+ res.body,
1148
+ 'Runo#call should include the current message'
1149
+ )
1150
+
1151
+ res = Rack::MockRequest.new(@runo).get(
1152
+ "http://example.com/#{tid}/#{new_id}index.html"
1153
+ )
1154
+ assert_no_match(
1155
+ /created 1 entry\./,
1156
+ res.body,
1157
+ 'Runo#call should not include the message twice'
1158
+ )
1159
+
1160
+ res.headers['Location'] =~ Runo::REX::PATH_ID
1161
+ new_id = sprintf('%.8d_%.4d', $1, $2)
1162
+ res = Rack::MockRequest.new(@runo).post(
1163
+ 'http://example.com/t_store/main/update.html',
1164
+ {
1165
+ :input => "#{new_id}-comment=howdy.&.status-public=update&_token=#{Runo.token}"
1166
+ }
1167
+ )
1168
+ res = Rack::MockRequest.new(@runo).get(
1169
+ res.headers['Location']
1170
+ )
1171
+ assert_match(
1172
+ /updated 1 entry\./,
1173
+ res.body,
1174
+ 'Runo#call should include a message according to the action'
1175
+ )
1176
+
1177
+ res = Rack::MockRequest.new(@runo).post(
1178
+ 'http://example.com/t_store/main/update.html',
1179
+ {
1180
+ :input => "#{new_id}.action=delete&.status-public=delete&_token=#{Runo.token}"
1181
+ }
1182
+ )
1183
+ res = Rack::MockRequest.new(@runo).get(
1184
+ res.headers['Location']
1185
+ )
1186
+ assert_match(
1187
+ /deleted 1 entry\./,
1188
+ res.body,
1189
+ 'Runo#call should include a message according to the action'
1190
+ )
1191
+ end
1192
+
1193
+ def test_message_notice_plural
1194
+ Runo.client = 'root'
1195
+
1196
+ res = Rack::MockRequest.new(@runo).post(
1197
+ 'http://example.com/t_attachment/main/update.html',
1198
+ {
1199
+ :input => "_1-comment=foo&_2-comment=bar&.status-public=create&_token=#{Runo.token}"
1200
+ }
1201
+ )
1202
+ res = Rack::MockRequest.new(@runo).get(
1203
+ res.headers['Location']
1204
+ )
1205
+ assert_match(
1206
+ /created 2 tArticles\./,
1207
+ res.body,
1208
+ 'the message should be plural if more than one item have results.'
1209
+ )
1210
+ end
1211
+
1212
+ def test_message_alert
1213
+ Runo.client = 'nobody'
1214
+
1215
+ res = Rack::MockRequest.new(@runo).get(
1216
+ "http://example.com/foo/20091120/1/update.html"
1217
+ )
1218
+ assert_match(
1219
+ /please login\./,
1220
+ res.body,
1221
+ 'Runo#call should include the current message'
1222
+ )
1223
+ end
1224
+
1225
+ def test_message_error
1226
+ Runo.client = 'root'
1227
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
1228
+
1229
+ res = Rack::MockRequest.new(@runo).post(
1230
+ 'http://example.com/t_store/main/update.html',
1231
+ {
1232
+ :input => "_2-name=verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrylongname&.status-public=create&_token=#{Runo.token}"
1233
+ }
1234
+ )
1235
+ assert_match(
1236
+ /malformed input\./,
1237
+ res.body,
1238
+ 'Runo#call should include the current message'
1239
+ )
1240
+ end
1241
+
1242
+ def test_message_i18n
1243
+ Runo.client = 'root'
1244
+ Runo::Set::Static::Folder.root.item('t_store', 'main').storage.clear
1245
+
1246
+ res = Rack::MockRequest.new(@runo).post(
1247
+ 'http://example.com/t_store/main/update.html',
1248
+ {
1249
+ :input => "_3-name=&.status-public=create&_token=#{Runo.token}",
1250
+ 'HTTP_ACCEPT_LANGUAGE' => 'en, de',
1251
+ }
1252
+ )
1253
+ assert_match(
1254
+ /malformed input/,
1255
+ res.body,
1256
+ "Runo::I18n.find_msg should return at least an empty hash for 'en' as HTTP_ACCEPT_LANGUAGE."
1257
+ )
1258
+
1259
+ res = Rack::MockRequest.new(@runo).post(
1260
+ 'http://example.com/t_store/main/update.html',
1261
+ {
1262
+ :input => "_3-name=&.status-public=create&_token=#{Runo.token}",
1263
+ 'HTTP_ACCEPT_LANGUAGE' => 'en-US, de',
1264
+ }
1265
+ )
1266
+ assert_match(
1267
+ /malformed input/,
1268
+ res.body,
1269
+ "Runo::I18n.find_msg should return at least an empty hash for 'en' as HTTP_ACCEPT_LANGUAGE."
1270
+ )
1271
+
1272
+ res = Rack::MockRequest.new(@runo).post(
1273
+ 'http://example.com/t_store/main/update.html',
1274
+ {
1275
+ :input => "_3-name=&.status-public=create&_token=#{Runo.token}",
1276
+ 'HTTP_ACCEPT_LANGUAGE' => 'de, en',
1277
+ }
1278
+ )
1279
+ assert_match(
1280
+ /Fehlerhafte Eingabe/,
1281
+ res.body,
1282
+ 'Set::Dynamic#_g_message should be i18nized according to HTTP_ACCEPT_LANGUAGE.'
1283
+ )
1284
+ end
1285
+
1286
+ end