runo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,900 @@
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_File < Test::Unit::TestCase
9
+
10
+ def setup
11
+ Runo.current[:uri] = nil
12
+
13
+ @file = Class.new
14
+ @file.stubs(:rewind).returns(nil)
15
+ @file.stubs(:read).returns('this is file body')
16
+ @file.stubs(:length).returns(@file.read.length)
17
+
18
+ meta = nil
19
+ Runo::Parser.gsub_scalar('$(foo file 1..50 jpg, gif, png)') {|id, m|
20
+ meta = m
21
+ ''
22
+ }
23
+ @f = Runo::Field.instance meta.merge(:id => 'foo')
24
+ end
25
+
26
+ def test_meta
27
+ assert_equal(
28
+ 1,
29
+ @f[:min],
30
+ 'File#initialize should set :min from the range token'
31
+ )
32
+ assert_equal(
33
+ 50,
34
+ @f[:max],
35
+ 'File#initialize should set :max from the range token'
36
+ )
37
+ assert_equal(
38
+ ['jpg', 'gif', 'png'],
39
+ @f[:options],
40
+ 'File#initialize should set :options from the csv token'
41
+ )
42
+ end
43
+
44
+ def test_meta_options
45
+ meta = nil
46
+ Runo::Parser.gsub_scalar('$(foo file jpg, GIF, Png)') {|id, m|
47
+ meta = m
48
+ ''
49
+ }
50
+ @f = Runo::Field.instance meta.merge(:id => 'foo')
51
+ assert_equal(
52
+ ['jpg', 'gif', 'png'],
53
+ @f[:options],
54
+ 'File#initialize should downcase :options'
55
+ )
56
+ end
57
+
58
+ def test_meta_path
59
+ @f[:parent] = Runo::Set::Static::Folder.root.item('t_file', 'main')
60
+ assert_equal(
61
+ '/t_file/main/foo',
62
+ @f[:path],
63
+ 'File#meta_path should return the full path to the field'
64
+ )
65
+ end
66
+
67
+ def test_meta_tmp_path
68
+ @f[:parent] = Runo::Set::Static::Folder.root.item('t_file', 'main')
69
+ Runo.current[:base] = @f[:parent]
70
+ tid = @f[:parent][:tid]
71
+
72
+ assert_equal(
73
+ "/t_file/#{tid}/foo",
74
+ @f[:tmp_path],
75
+ 'File#meta_tmp_path should return the short path from the tid'
76
+ )
77
+
78
+ Runo.current[:base] = nil
79
+ assert_nil(
80
+ @f[:tmp_path],
81
+ 'File#meta_tmp_path should return nil unless Runo.base is set'
82
+ )
83
+ end
84
+
85
+ def test_meta_persistent_sd
86
+ root = Runo::Set::Static::Folder.root.item('t_file', 'main')
87
+ parent = Runo::Set::Dynamic.new(:id => 'boo', :parent => root)
88
+ @f[:parent] = parent
89
+ assert_equal(
90
+ root,
91
+ @f[:persistent_sd],
92
+ 'File#persistent_sd should return the nearest persient sd'
93
+ )
94
+ end
95
+
96
+ def test_meta_persistent_name
97
+ root = Runo::Set::Static::Folder.root.item('t_file', 'main')
98
+ parent = Runo::Set::Dynamic.new(:id => 'boo', :parent => root)
99
+ @f[:parent] = parent
100
+ assert_equal(
101
+ 'boo-foo',
102
+ @f[:persistent_name],
103
+ 'File#persistent_name should return the path to [:persistent_sd]'
104
+ )
105
+ end
106
+
107
+ def test_val_cast_from_rack
108
+ @f.create(
109
+ :type => 'image/jpeg',
110
+ :tempfile => @file,
111
+ :head => <<'_eos',
112
+ Content-Disposition: form-data; name="t_file"; filename="baz.jpg"
113
+ Content-Type: image/jpeg
114
+ _eos
115
+ :filename => 'baz.jpg',
116
+ :name => 't_file'
117
+ )
118
+
119
+ assert_equal(
120
+ {
121
+ 'basename' => 'baz.jpg',
122
+ 'type' => 'image/jpeg',
123
+ 'size' => @file.length,
124
+ },
125
+ @f.val,
126
+ 'File#val_cast should re-map a hash from Rack'
127
+ )
128
+ assert_equal(
129
+ @file.read,
130
+ @f.body,
131
+ 'File#val_cast should store the file body in @body'
132
+ )
133
+ end
134
+
135
+ def test_val_cast_load
136
+ @f.load(
137
+ 'basename' => 'baz.jpg',
138
+ 'type' => 'image/jpeg',
139
+ 'size' => 123
140
+ )
141
+ assert_equal(
142
+ {
143
+ 'basename' => 'baz.jpg',
144
+ 'type' => 'image/jpeg',
145
+ 'size' => 123,
146
+ },
147
+ @f.val,
148
+ 'File#val_cast should load() a hash without :tempfile like Set#load'
149
+ )
150
+
151
+ @f.load(
152
+ {}
153
+ )
154
+ assert_equal(
155
+ {},
156
+ @f.val,
157
+ 'File#val_cast should load() a hash without :tempfile like Set#load'
158
+ )
159
+ end
160
+
161
+ def test_get
162
+ @f[:parent] = Runo::Set::Static::Folder.root.item('t_file', 'main')
163
+ Runo.current[:base] = @f[:parent]
164
+ tid = @f[:parent][:tid]
165
+
166
+ @f.load({})
167
+ assert_nil(
168
+ @f.get,
169
+ 'File#get should return nil when the val is empty'
170
+ )
171
+
172
+ @f.load(
173
+ 'basename' => 'baz.jpg',
174
+ 'type' => 'image/jpeg',
175
+ 'size' => 12
176
+ )
177
+ assert_equal(
178
+ '<a href="/t_file/main/foo/baz.jpg">baz.jpg (12 bytes)</a>',
179
+ @f.get,
180
+ 'File#get should return proper string'
181
+ )
182
+ assert_equal(
183
+ <<"_html",
184
+ <span class="file">
185
+ <a href="/t_file/#{tid}/foo/baz.jpg">baz.jpg (12 bytes)</a>
186
+ <input type="file" name="foo" size="" class="file" />
187
+ </span>
188
+ _html
189
+ @f.get(:action => :update),
190
+ 'File#get should return proper string'
191
+ )
192
+
193
+ @f.load(
194
+ 'basename' => '<baz>.jpg',
195
+ 'type' => 'image/<jpeg>',
196
+ 'size' => 12
197
+ )
198
+ assert_equal(
199
+ '<a href="/t_file/main/foo/&lt;baz&gt;.jpg">&lt;baz&gt;.jpg (12 bytes)</a>',
200
+ @f.get,
201
+ 'File#get should escape the special characters in file information'
202
+ )
203
+ end
204
+
205
+ def test_get_hidden
206
+ Runo.client = 'root'
207
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
208
+ sd.update(
209
+ '_1' => {
210
+ 'baz' => {
211
+ '_1' => {},
212
+ },
213
+ }
214
+ )
215
+ Runo.current[:base] = sd
216
+ sd[:tid] = '1234.567'
217
+
218
+ assert_equal(
219
+ <<'_html',
220
+ <span class="file">
221
+
222
+ <input type="file" name="_1-foo" size="" class="file" />
223
+ </span>
224
+ _html
225
+ sd.item('_1', 'foo').get(:action => :create),
226
+ 'File#get should not include a hidden input if the field is not required'
227
+ )
228
+ assert_equal(
229
+ <<'_html',
230
+ <span class="file">
231
+
232
+ <input type="hidden" name="_1-baz-_1-qux" value="" />
233
+ <input type="file" name="_1-baz-_1-qux" size="" class="file" />
234
+ </span>
235
+ _html
236
+ sd.item('_1', 'baz', '_1', 'qux').get(:action => :create),
237
+ 'File#get should include a hidden input to supplement an empty field'
238
+ )
239
+
240
+ sd.update(
241
+ '_1' => {
242
+ 'baz' => {
243
+ '_1' => {
244
+ 'qux' => {
245
+ :type => 'image/jpeg',
246
+ :tempfile => @file,
247
+ :head => <<'_eos',
248
+ Content-Disposition: form-data; name="t_file"; filename="qux.jpg"
249
+ Content-Type: image/jpeg
250
+ _eos
251
+ :filename => 'qux.jpg',
252
+ :name => 't_file'
253
+ },
254
+ },
255
+ },
256
+ }
257
+ )
258
+ assert_equal(
259
+ <<"_html",
260
+ <span class="file">
261
+ <a href="/t_file/1234.567/_1/baz/_1/qux/qux.jpg">qux.jpg (#{@file.length} bytes)</a>
262
+ <input type="file" name="_1-baz-_1-qux" size="" class="file" />
263
+ </span>
264
+ _html
265
+ sd.item('_1', 'baz', '_1', 'qux').get(:action => :update),
266
+ 'File#get should not include a hidden if the field is not empty'
267
+ )
268
+ end
269
+
270
+ def test_get_delete
271
+ Runo.client = 'root'
272
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
273
+ sd.update(
274
+ '_1' => {
275
+ 'baz' => {
276
+ '_1' => {},
277
+ },
278
+ }
279
+ )
280
+ Runo.current[:base] = sd
281
+ sd[:tid] = '1234.567'
282
+
283
+ assert_equal(
284
+ <<'_html',
285
+ <span class="file">
286
+
287
+ <input type="file" name="_1-foo" size="" class="file" />
288
+ </span>
289
+ _html
290
+ sd.item('_1', 'foo').get(:action => :create),
291
+ 'File#get should not include a delete submit if the field is empty'
292
+ )
293
+
294
+ sd.update(
295
+ '_1' => {
296
+ 'foo' => {
297
+ :type => 'image/jpeg',
298
+ :tempfile => @file,
299
+ :head => <<'_eos',
300
+ Content-Disposition: form-data; name="t_file"; filename="foo.jpg"
301
+ Content-Type: image/jpeg
302
+ _eos
303
+ :filename => 'foo.jpg',
304
+ :name => 't_file'
305
+ },
306
+ }
307
+ )
308
+ assert_equal(
309
+ <<"_html",
310
+ <span class="file">
311
+ <a href="/t_file/1234.567/_1/foo/foo.jpg">foo.jpg (#{@file.length} bytes)</a>
312
+ <input type="file" name="_1-foo" size="" class="file" />
313
+ <input type="submit" name="_1-foo.action-delete" value="delete" />
314
+ </span>
315
+ _html
316
+ sd.item('_1', 'foo').get(:action => :update),
317
+ 'File#get should include a delete submit if the field is not empty'
318
+ )
319
+
320
+ sd.item('_1', 'foo')[:min] = 1
321
+ assert_equal(
322
+ <<"_html",
323
+ <span class="file">
324
+ <a href="/t_file/1234.567/_1/foo/foo.jpg">foo.jpg (#{@file.length} bytes)</a>
325
+ <input type="file" name="_1-foo" size="" class="file" />
326
+ </span>
327
+ _html
328
+ sd.item('_1', 'foo').get(:action => :update),
329
+ 'File#get should not include a delete submit if the field is mandatory'
330
+ )
331
+ end
332
+
333
+ # TODO: test on all available storages.
334
+ def test_select
335
+ Runo.client = 'root'
336
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
337
+ sd.storage.clear
338
+ sd.update(
339
+ '20100425_1234' => {
340
+ 'foo' => {
341
+ :type => 'image/jpeg',
342
+ :tempfile => @file,
343
+ :filename => 'foo.jpg',
344
+ :name => 't_file'
345
+ },
346
+ }
347
+ ).commit :persistent
348
+
349
+ assert_equal(
350
+ ['20100425_1234'],
351
+ sd.collect {|item| item[:id] },
352
+ 'storages should distinguish between data and files'
353
+ )
354
+ assert_equal(
355
+ ['20100425_1234'],
356
+ sd.instance_eval {
357
+ collect_item(:id => '20100425_1234') {|item| item[:id] }
358
+ },
359
+ 'storages should distinguish between data and files'
360
+ )
361
+ assert_equal(
362
+ ['20100425_1234'],
363
+ sd.instance_eval {
364
+ collect_item(:d => '201004') {|item| item[:id] }
365
+ },
366
+ 'storages should distinguish between data and files'
367
+ )
368
+ assert_equal(
369
+ ['20100425_1234'],
370
+ sd.instance_eval {
371
+ collect_item({}) {|item| item[:id] }
372
+ },
373
+ 'storages should distinguish between data and files'
374
+ )
375
+ end
376
+
377
+ def test_call_body
378
+ Runo.client = 'root'
379
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
380
+ sd.storage.clear
381
+
382
+ # post a multipart request
383
+ input = <<"_eos".gsub(/\r?\n/, "\r\n")
384
+ ---foobarbaz
385
+ Content-Disposition: form-data; name="_1-foo"; filename="foo.jpg"
386
+ Content-Type: image/jpeg
387
+
388
+ #{@file.read}
389
+ ---foobarbaz
390
+ Content-Disposition: form-data; name="_token"
391
+
392
+ #{Runo.token}
393
+ ---foobarbaz--
394
+ _eos
395
+ res = Rack::MockRequest.new(Runo.new).post(
396
+ 'http://example.com/t_file/main/update.html',
397
+ {
398
+ :input => input,
399
+ 'CONTENT_TYPE' => 'multipart/form-data; boundary=-foobarbaz',
400
+ 'CONTENT_LENGTH' => input.length,
401
+ }
402
+ )
403
+ tid = res.headers['Location'][Runo::REX::TID]
404
+
405
+ assert_equal(
406
+ @file.read,
407
+ Runo.transaction[tid].item('_1', 'foo').body,
408
+ 'Runo#call should keep suspended field in Runo.transaction'
409
+ )
410
+
411
+ res = Rack::MockRequest.new(Runo.new).get(
412
+ "http://example.com/#{tid}/_1/foo/foo.jpg"
413
+ )
414
+ assert_equal(
415
+ @file.read,
416
+ res.body,
417
+ 'Runo#call should be able to access suspended file bodies'
418
+ )
419
+
420
+ # commit the base
421
+ res = Rack::MockRequest.new(Runo.new).post(
422
+ "http://example.com/#{tid}/update.html",
423
+ {
424
+ :input => ".status-public=create&_token=#{Runo.token}",
425
+ }
426
+ )
427
+
428
+ res.headers['Location'] =~ Runo::REX::PATH_ID
429
+ new_id = sprintf('%.8d_%.4d', $1, $2)
430
+
431
+ res = Rack::MockRequest.new(Runo.new).get(
432
+ "http://example.com/t_file/#{new_id}/foo/foo.jpg"
433
+ )
434
+ assert_equal(
435
+ 'image/jpeg',
436
+ res.headers['Content-Type'],
437
+ 'Runo#call to a file item should return the mime type of the file'
438
+ )
439
+ assert_equal(
440
+ @file.length.to_s,
441
+ res.headers['Content-Length'],
442
+ 'Runo#call to a file item should return the content length of the file'
443
+ )
444
+ assert_equal(
445
+ @file.read,
446
+ res.body,
447
+ 'Runo#call to a file item should return the binary body of the file'
448
+ )
449
+
450
+ # move
451
+ Rack::MockRequest.new(Runo.new).post(
452
+ 'http://example.com/t_file/update.html',
453
+ {
454
+ :input => "#{new_id}-_timestamp=1981-12-02&.status-public=update&_token=#{Runo.token}",
455
+ }
456
+ )
457
+ res = Rack::MockRequest.new(Runo.new).get(
458
+ "http://example.com/t_file/#{new_id}/foo/foo.jpg"
459
+ )
460
+ assert_equal(
461
+ 404,
462
+ res.status,
463
+ 'Runo#call should move child files as well'
464
+ )
465
+ res = Rack::MockRequest.new(Runo.new).get(
466
+ 'http://example.com/t_file/19811202_0001/foo/foo.jpg'
467
+ )
468
+ assert_equal(
469
+ @file.read,
470
+ res.body,
471
+ 'Runo#call should move child files as well'
472
+ )
473
+
474
+ # delete
475
+ Rack::MockRequest.new(Runo.new).post(
476
+ 'http://example.com/t_file/update.html',
477
+ {
478
+ :input => "19811202_0001.action=delete&.status-public=delete&_token=#{Runo.token}",
479
+ }
480
+ )
481
+ res = Rack::MockRequest.new(Runo.new).get(
482
+ 'http://example.com/t_file/19811202_0001/foo/foo.jpg'
483
+ )
484
+ assert_equal(
485
+ 404,
486
+ res.status,
487
+ 'Runo#call should delete child files as well'
488
+ )
489
+ end
490
+
491
+ def test_errors
492
+ @f.create({})
493
+ @f[:min] = 0
494
+ assert_equal(
495
+ [],
496
+ @f.errors,
497
+ 'File#errors should return the errors of the current body'
498
+ )
499
+
500
+ @f[:min] = 1
501
+ @f.create({})
502
+ assert_equal(
503
+ ['mandatory'],
504
+ @f.errors,
505
+ 'File#errors should return the errors of the current body'
506
+ )
507
+
508
+ @f[:min] = 1
509
+ @f.delete
510
+ @f.commit :temp
511
+ @f.commit :persistent
512
+ assert_equal(
513
+ ['mandatory'],
514
+ @f.errors,
515
+ 'File#errors should return the errors of the current body'
516
+ )
517
+
518
+ @f.update(
519
+ :type => 'image/jpeg',
520
+ :tempfile => @file,
521
+ :head => <<'_eos',
522
+ Content-Disposition: form-data; name="t_file"; filename="baz.jpg"
523
+ Content-Type: image/jpeg
524
+ _eos
525
+ :filename => 'baz.jpg',
526
+ :name => 't_file'
527
+ )
528
+
529
+ @f[:min] = 0
530
+ assert_equal(
531
+ [],
532
+ @f.errors,
533
+ 'File#errors should return the errors of the current body'
534
+ )
535
+
536
+ @f[:min] = @file.length + 1
537
+ @f[:max] = nil
538
+ assert_equal(
539
+ ["too small: #{@file.length + 1} bytes minimum"],
540
+ @f.errors,
541
+ 'File#errors should return the errors of the current body'
542
+ )
543
+
544
+ @f[:min] = nil
545
+ @f[:max] = @file.length - 1
546
+ assert_equal(
547
+ ["too large: #{@file.length - 1} bytes maximum"],
548
+ @f.errors,
549
+ 'File#errors should return the errors of the current body'
550
+ )
551
+
552
+ @f[:min] = nil
553
+ @f[:max] = nil
554
+ @f[:options] = ['txt', 'pdf', 'doc']
555
+ assert_equal(
556
+ ['wrong file type: should be txt/pdf/doc'],
557
+ @f.errors,
558
+ 'File#errors should return the errors of the current body'
559
+ )
560
+ end
561
+
562
+ def test_errors_options
563
+ @f.create(
564
+ :type => 'image/jpeg',
565
+ :tempfile => @file,
566
+ :head => <<'_eos',
567
+ Content-Disposition: form-data; name="t_file"; filename="BAZ.JPG"
568
+ Content-Type: image/jpeg
569
+ _eos
570
+ :filename => 'BAZ.JPG',
571
+ :name => 't_file'
572
+ )
573
+
574
+ @f[:min] = nil
575
+ @f[:max] = nil
576
+ @f[:options] = ['jpg']
577
+ assert_equal(
578
+ [],
579
+ @f.errors,
580
+ 'File#errors should ignore the case of extentions'
581
+ )
582
+ end
583
+
584
+ def test_save_file
585
+ Runo.client = 'root'
586
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
587
+ sd.storage.clear
588
+
589
+ # create a new set with file items
590
+ sd.update(
591
+ '_1' => {
592
+ 'foo' => {
593
+ :type => 'image/jpeg',
594
+ :tempfile => @file,
595
+ :head => <<'_eos',
596
+ Content-Disposition: form-data; name="t_file"; filename="foo.jpg"
597
+ Content-Type: image/jpeg
598
+ _eos
599
+ :filename => 'foo.jpg',
600
+ :name => 't_file'
601
+ },
602
+ }
603
+ )
604
+ assert_nothing_raised(
605
+ 'File#commit should commit files nicely'
606
+ ) {
607
+ sd.commit :persistent
608
+ }
609
+ id = sd.result.values.first[:id]
610
+
611
+ item = Runo::Set::Static::Folder.root.item('t_file', 'main', id, 'foo')
612
+ assert_instance_of(
613
+ Runo::File,
614
+ item,
615
+ 'File#commit should commit the file item'
616
+ )
617
+ assert_equal(
618
+ @file.read,
619
+ item.body,
620
+ 'File#commit should store the body of the file item'
621
+ )
622
+
623
+ # update the sibling file item
624
+ sd.update(
625
+ id => {
626
+ 'bar' => {
627
+ :type => 'image/png',
628
+ :tempfile => @file,
629
+ :head => <<'_eos',
630
+ Content-Disposition: form-data; name="t_file"; filename="bar.png"
631
+ Content-Type: image/png
632
+ _eos
633
+ :filename => 'bar.png',
634
+ :name => 't_file'
635
+ },
636
+ }
637
+ )
638
+ sd.commit :persistent
639
+
640
+ assert_equal(
641
+ @file.read,
642
+ Runo::Set::Static::Folder.root.item('t_file', 'main', id, 'bar').body,
643
+ 'File#commit should store the body of the file item'
644
+ )
645
+ assert_equal(
646
+ @file.read,
647
+ Runo::Set::Static::Folder.root.item('t_file', 'main', id, 'foo').body,
648
+ 'File#commit should keep the body of the untouched file item'
649
+ )
650
+ end
651
+
652
+ def test_delete_file
653
+ Runo.client = 'root'
654
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
655
+ sd.storage.clear
656
+
657
+ sd.update(
658
+ '_1' => {
659
+ 'foo' => {
660
+ :type => 'image/jpeg',
661
+ :tempfile => @file,
662
+ :head => <<'_eos',
663
+ Content-Disposition: form-data; name="t_file"; filename="foo.jpg"
664
+ Content-Type: image/jpeg
665
+ _eos
666
+ :filename => 'foo.jpg',
667
+ :name => 't_file'
668
+ },
669
+ }
670
+ )
671
+ sd.commit :persistent
672
+
673
+ id = sd.result.values.first[:id]
674
+
675
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
676
+ sd.update(
677
+ id => {'foo' => {:action => :delete}}
678
+ )
679
+ assert_equal(:delete, sd.item(id, 'foo').action)
680
+ sd.commit :temp
681
+
682
+ assert_equal(
683
+ {},
684
+ sd.item(id, 'foo').val,
685
+ 'File#delete should clear the val of the field'
686
+ )
687
+ assert_equal(
688
+ :delete,
689
+ sd.item(id, 'foo').action,
690
+ 'File#delete should set @action'
691
+ )
692
+
693
+ another_sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
694
+ assert_not_equal(
695
+ {},
696
+ another_sd.item(id, 'foo').val,
697
+ 'File#delete should not clear the persistent val before commit(:persistent)'
698
+ )
699
+ assert_equal(
700
+ @file.read,
701
+ another_sd.item(id, 'foo').body,
702
+ 'File#delete should not delete the persistent body before commit(:persistent)'
703
+ )
704
+
705
+ sd.commit :persistent
706
+
707
+ another_sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
708
+ assert_equal(
709
+ {},
710
+ another_sd.item(id, 'foo').val,
711
+ 'File#delete should clear the persistent val after commit(:persistent)'
712
+ )
713
+ assert_nil(
714
+ another_sd.item(id, 'foo').body,
715
+ 'File#delete should clear the persistent body after commit(:persistent)'
716
+ )
717
+ end
718
+
719
+ def test_delete_parent_set
720
+ Runo.client = 'root'
721
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
722
+ sd.storage.clear
723
+
724
+ sd.update(
725
+ '_1' => {
726
+ 'foo' => {
727
+ :type => 'image/jpeg',
728
+ :tempfile => @file,
729
+ :head => <<'_eos',
730
+ Content-Disposition: form-data; name="t_file"; filename="foo.jpg"
731
+ Content-Type: image/jpeg
732
+ _eos
733
+ :filename => 'baz.jpg',
734
+ :name => 't_file'
735
+ },
736
+ 'bar' => {
737
+ :type => 'image/jpeg',
738
+ :tempfile => @file,
739
+ :head => <<'_eos',
740
+ Content-Disposition: form-data; name="t_file"; filename="bar.jpg"
741
+ Content-Type: image/jpeg
742
+ _eos
743
+ :filename => 'bar.jpg',
744
+ :name => 't_file'
745
+ },
746
+ }
747
+ )
748
+ sd.commit :persistent
749
+
750
+ id = sd.result.values.first[:id]
751
+
752
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
753
+ sd.update(
754
+ id => {:action => :delete}
755
+ )
756
+ sd.commit :persistent
757
+
758
+ assert_equal(
759
+ {},
760
+ sd.val,
761
+ 'Set#delete should delete body of the file items'
762
+ )
763
+ end
764
+
765
+ def test_save_file_attachment
766
+ Runo.client = 'root'
767
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
768
+ sd.storage.clear
769
+
770
+ # create an attachment file item
771
+ sd.update(
772
+ '_1' => {
773
+ 'baz' => {
774
+ '_1' => {
775
+ 'qux' => {
776
+ :type => 'image/gif',
777
+ :tempfile => @file,
778
+ :head => <<'_eos',
779
+ Content-Disposition: form-data; name="t_file"; filename="qux.gif"
780
+ Content-Type: image/gif
781
+ _eos
782
+ :filename => 'qux.gif',
783
+ :name => 't_file'
784
+ },
785
+ },
786
+ }
787
+ }
788
+ )
789
+
790
+ sd.commit :persistent
791
+ baz_id = sd.result.values.first[:id]
792
+ qux_id = sd.result.values.first.item('baz').val.keys.first
793
+
794
+ item = Runo::Set::Static::Folder.root.item('t_file', 'main', baz_id, 'baz', qux_id, 'qux')
795
+ assert_instance_of(
796
+ Runo::File,
797
+ item,
798
+ 'File#commit should commit the attachment file item'
799
+ )
800
+ assert_equal(
801
+ @file.read,
802
+ item.body,
803
+ 'File#commit should store the body of the attachment file item'
804
+ )
805
+
806
+ # delete the attachment
807
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
808
+ sd.update(
809
+ baz_id => {
810
+ 'baz' => {
811
+ qux_id => {:action => :delete},
812
+ }
813
+ }
814
+ )
815
+ sd.commit :persistent
816
+
817
+ assert_equal(
818
+ {},
819
+ Runo::Set::Static::Folder.root.item('t_file', 'main', baz_id, 'baz').storage.val,
820
+ 'File#commit should delete the body of the attachment file item'
821
+ )
822
+ end
823
+
824
+ def test_delete_attachment_parent
825
+ Runo.client = 'root'
826
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
827
+ sd.storage.clear
828
+
829
+ # create an attachment file item
830
+ sd.update(
831
+ '_1' => {
832
+ 'baz' => {
833
+ '_1' => {
834
+ 'qux' => {
835
+ :type => 'image/gif',
836
+ :tempfile => @file,
837
+ :head => <<'_eos',
838
+ Content-Disposition: form-data; name="t_file"; filename="qux.gif"
839
+ Content-Type: image/gif
840
+ _eos
841
+ :filename => 'qux.gif',
842
+ :name => 't_file'
843
+ },
844
+ },
845
+ }
846
+ }
847
+ )
848
+
849
+ sd.commit :persistent
850
+ assert_not_equal({}, sd.val)
851
+
852
+ baz_id = sd.result.values.first[:id]
853
+ qux_id = sd.result.values.first.item('baz').val.keys.first
854
+
855
+ item = Runo::Set::Static::Folder.root.item('t_file', 'main', baz_id, 'baz', qux_id, 'qux')
856
+ item_persistent_name = item[:persistent_name]
857
+ assert_equal(
858
+ @file.read,
859
+ Runo::Set::Static::Folder.root.item('t_file', 'main').storage.val(item_persistent_name),
860
+ 'the body of the file should be stored in the storage'
861
+ )
862
+
863
+ # delete the parent set
864
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
865
+ sd[:item]['default'][:item].delete '_timestamp'
866
+ sd.update(
867
+ baz_id => {:action => :delete}
868
+ )
869
+
870
+ sd.commit :temp
871
+ assert_equal(
872
+ <<"_eos",
873
+ <"main" @action=:update @result={"#{baz_id}"}>
874
+ <"#{baz_id}" @action=:delete @result=:delete>
875
+ <"_group" @action=nil @result=nil @val=nil>
876
+ <"_owner" @action=nil @result=nil @val="root">
877
+ <"bar" @action=:delete @result=:delete @val={}>
878
+ <"baz" @action=:delete @result=:delete>
879
+ <"foo" @action=:delete @result=:delete @val={}>
880
+ _eos
881
+ sd.inspect_items
882
+ )
883
+
884
+ sd.commit :persistent
885
+ assert_equal(
886
+ <<"_eos",
887
+ <"main" @action=nil @result={"#{baz_id}"}>
888
+ _eos
889
+ sd.inspect_items
890
+ )
891
+
892
+ sd = Runo::Set::Static::Folder.root.item('t_file', 'main')
893
+ assert_equal({}, sd.val)
894
+ assert_nil(
895
+ Runo::Set::Static::Folder.root.item('t_file', 'main').storage.val(item_persistent_name),
896
+ 'the body of the file should be deleted from the storage'
897
+ )
898
+ end
899
+
900
+ end