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,527 @@
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_Set_Complex < Test::Unit::TestCase
9
+
10
+ class ::Runo::Set::Dynamic
11
+ def _g_vegetable(arg)
12
+ "'potato'"
13
+ end
14
+ end
15
+
16
+ class ::Runo::Workflow::Pipco < ::Runo::Workflow
17
+ DEFAULT_SUB_ITEMS = {
18
+ '_owner' => {:klass => 'meta-owner'},
19
+ }
20
+ PERM = {
21
+ :create => 0b11000,
22
+ :read => 0b11110,
23
+ :update => 0b11100,
24
+ :delete => 0b10100,
25
+ }
26
+ def _g_submit(arg)
27
+ '[pipco]'
28
+ end
29
+ end
30
+
31
+ class ::Runo::Tomago < ::Runo::Field
32
+ def _get(arg)
33
+ args = arg.keys.collect {|k| "#{k}=#{arg[k]}" }.sort
34
+ "'#{val}'(#{args.join ', '})"
35
+ end
36
+ end
37
+
38
+ def setup
39
+ # Set::Dynamic of Set::Static of (Scalar and (Set::Dynamic of Set::Static of Scalar))
40
+ @sd = Runo::Set::Dynamic.new(
41
+ :id => 'main',
42
+ :klass => 'set-dynamic',
43
+ :workflow => 'pipco',
44
+ :group => ['roy', 'don'],
45
+ :tmpl => {
46
+ :index => <<'_tmpl'.chomp,
47
+ <ul id="@(name)" class="runo-pipco">
48
+ $()</ul>
49
+ $(.navi)$(.submit)$(.action_create)
50
+ _tmpl
51
+ },
52
+ :item => {
53
+ 'default' => Runo::Parser.parse_html(<<'_html')
54
+ <li id="@(name)">
55
+ $(name = tomago 32 :'nobody'): $(comment = tomago 64 :'hello.')
56
+ <ul id="files" class="runo-attachment">
57
+ <li id="@(name)">$(file = tomago :'foo.jpg')</li>
58
+ </ul>
59
+ <ul id="replies" class="runo-pipco">
60
+ <li id="@(name)">$(reply = tomago :'hi.')</li>
61
+ </ul>
62
+ $(replies.vegetable)
63
+ </li>
64
+ _html
65
+ }
66
+ )
67
+ @sd.load(
68
+ '20091123_0001' => {
69
+ '_owner' => 'carl',
70
+ 'name' => 'CZ',
71
+ 'comment' => 'oops',
72
+ 'files' => {
73
+ '20091123_0001' => {'file' => 'carl1.jpg'},
74
+ '20091123_0002' => {'file' => 'carl2.jpg'},
75
+ },
76
+ 'replies' => {
77
+ '20091125_0001' => {'_owner' => 'bobby', 'reply' => 'howdy.'},
78
+ },
79
+ },
80
+ '20091123_0002' => {
81
+ '_owner' => 'roy',
82
+ 'name' => 'RE',
83
+ 'comment' => 'wee',
84
+ 'files' => {
85
+ '20091123_0001' => {'file' => 'roy.png'},
86
+ },
87
+ 'replies' => {
88
+ '20091125_0001' => {'_owner' => 'don', 'reply' => 'ho ho.'},
89
+ '20091125_0002' => {'_owner' => 'roy', 'reply' => 'oops.'},
90
+ },
91
+ }
92
+ )
93
+
94
+ [
95
+ @sd,
96
+ @sd.item('20091123_0001', 'files'),
97
+ @sd.item('20091123_0001', 'replies'),
98
+ @sd.item('20091123_0002', 'files'),
99
+ @sd.item('20091123_0002', 'replies'),
100
+ ].each {|sd|
101
+ sd[:tmpl][:action_create] = ''
102
+ sd[:tmpl][:navi] = ''
103
+ sd[:tmpl][:submit_create] = '[c]'
104
+ sd[:tmpl][:submit_delete] = '[d]'
105
+ def sd._g_submit(arg)
106
+ "[#{my[:id]}-#{arg[:orig_action]}]\n"
107
+ end
108
+
109
+ sd.each {|item|
110
+ item[:tmpl][:action_update] = ''
111
+ }
112
+ }
113
+ end
114
+
115
+ def teardown
116
+ Runo.client = nil
117
+ end
118
+
119
+ def test_get_default
120
+ Runo.client = 'root' #nil
121
+ result = @sd.get
122
+
123
+ assert_match(
124
+ /'potato'/,
125
+ result,
126
+ 'Set#get should include $(foo.baz) whenever the action :baz is permitted'
127
+ )
128
+ assert_equal(
129
+ <<'_html',
130
+ <ul id="main" class="runo-pipco">
131
+ <li id="main-20091123_0001">
132
+ 'CZ'(action=read, p_action=read): 'oops'(action=read, p_action=read)
133
+ <ul id="main-20091123_0001-files" class="runo-attachment">
134
+ <li id="main-20091123_0001-files-20091123_0001">'carl1.jpg'(action=read, p_action=read)</li>
135
+ <li id="main-20091123_0001-files-20091123_0002">'carl2.jpg'(action=read, p_action=read)</li>
136
+ </ul>
137
+ <ul id="main-20091123_0001-replies" class="runo-pipco">
138
+ <li id="main-20091123_0001-replies-20091125_0001"><a href="/20091123_0001/replies/20091125/1/update.html">'howdy.'(action=read, p_action=read)</a></li>
139
+ </ul>
140
+ 'potato'
141
+ </li>
142
+ <li id="main-20091123_0002">
143
+ 'RE'(action=read, p_action=read): 'wee'(action=read, p_action=read)
144
+ <ul id="main-20091123_0002-files" class="runo-attachment">
145
+ <li id="main-20091123_0002-files-20091123_0001">'roy.png'(action=read, p_action=read)</li>
146
+ </ul>
147
+ <ul id="main-20091123_0002-replies" class="runo-pipco">
148
+ <li id="main-20091123_0002-replies-20091125_0001"><a href="/20091123_0002/replies/20091125/1/update.html">'ho ho.'(action=read, p_action=read)</a></li>
149
+ <li id="main-20091123_0002-replies-20091125_0002"><a href="/20091123_0002/replies/20091125/2/update.html">'oops.'(action=read, p_action=read)</a></li>
150
+ </ul>
151
+ 'potato'
152
+ </li>
153
+ </ul>
154
+ _html
155
+ result,
156
+ 'Set#get should work recursively as a part of the complex'
157
+ )
158
+ end
159
+
160
+ def test_get_with_parent_action
161
+ Runo.client = 'root'
162
+ result = @sd.get(:action => :update)
163
+
164
+ assert_match(
165
+ /id="main-20091123_0001-files"/,
166
+ result,
167
+ 'Set::Dynamic#get(:action => :update) should include child attachments'
168
+ )
169
+ assert_no_match(
170
+ /id="main-20091123_0001-replies"/,
171
+ result,
172
+ 'Set::Dynamic#get(:action => :update) should not include child apps'
173
+ )
174
+ assert_no_match(
175
+ /'potato'/,
176
+ result,
177
+ 'Set::Dynamic#get(:action => :update) should not include any value of child apps'
178
+ )
179
+ assert_no_match(
180
+ /<form.+<form/m,
181
+ result,
182
+ 'Set::Dynamic#get(:action => :update) should not return nested forms'
183
+ )
184
+ assert_equal(
185
+ <<'_html',
186
+ <ul id="main" class="runo-pipco">
187
+ <li id="main-20091123_0001">
188
+ 'CZ'(action=update, p_action=update): 'oops'(action=update, p_action=update)
189
+ <ul id="main-20091123_0001-files" class="runo-attachment">
190
+ <li id="main-20091123_0001-files-20091123_0001">'carl1.jpg'(action=update, p_action=update)[d]</li>
191
+ <li id="main-20091123_0001-files-20091123_0002">'carl2.jpg'(action=update, p_action=update)[d]</li>
192
+ <li id="main-20091123_0001-files-_001">'foo.jpg'(action=create, p_action=create)[c]</li>
193
+ </ul>
194
+ </li>
195
+ <li id="main-20091123_0002">
196
+ 'RE'(action=update, p_action=update): 'wee'(action=update, p_action=update)
197
+ <ul id="main-20091123_0002-files" class="runo-attachment">
198
+ <li id="main-20091123_0002-files-20091123_0001">'roy.png'(action=update, p_action=update)[d]</li>
199
+ <li id="main-20091123_0002-files-_001">'foo.jpg'(action=create, p_action=create)[c]</li>
200
+ </ul>
201
+ </li>
202
+ </ul>
203
+ [main-update]
204
+ _html
205
+ result,
206
+ 'Set#get should distribute the action to its items'
207
+ )
208
+ end
209
+
210
+ def test_get_with_partial_permission
211
+ Runo.client = 'carl' # can edit only his own item
212
+
213
+ assert_raise(
214
+ Runo::Error::Forbidden,
215
+ 'Field#get should raise Error::Forbidden when an action is given but forbidden'
216
+ ) {
217
+ @sd.get(:action => :update, :conds => {:id => '20091123_0002'})
218
+ }
219
+
220
+ @sd.item('20091123_0002', 'comment')[:owner] = 'carl' # enclave in roy's item
221
+
222
+ assert_raise(
223
+ Runo::Error::Forbidden,
224
+ 'Field#get should not allow partially permitted get'
225
+ ) {
226
+ @sd.get(:action => :update, :conds => {:id => '20091123_0002'})
227
+ }
228
+ end
229
+
230
+ def test_get_with_partial_action
231
+ Runo.client = 'root'
232
+
233
+ Runo.current[:base] = @sd.item('20091123_0002', 'replies')
234
+ Runo.base[:tid] = '123.45'
235
+
236
+ result = @sd.get(
237
+ '20091123_0002' => {
238
+ 'replies' => {
239
+ :action => :update,
240
+ :conds => {:id => '20091125_0002'},
241
+ },
242
+ }
243
+ )
244
+ assert_equal(
245
+ <<_html,
246
+ <ul id="main" class="runo-pipco">
247
+ <li id="main-20091123_0001">
248
+ 'CZ'(action=read, p_action=read): 'oops'(action=read, p_action=read)
249
+ <ul id="main-20091123_0001-files" class="runo-attachment">
250
+ <li id="main-20091123_0001-files-20091123_0001">'carl1.jpg'(action=read, p_action=read)</li>
251
+ <li id="main-20091123_0001-files-20091123_0002">'carl2.jpg'(action=read, p_action=read)</li>
252
+ </ul>
253
+ <ul id="main-20091123_0001-replies" class="runo-pipco">
254
+ <li id="main-20091123_0001-replies-20091125_0001"><a href="/20091123_0001/replies/20091125/1/update.html">'howdy.'(action=read, p_action=read)</a></li>
255
+ </ul>
256
+ 'potato'
257
+ </li>
258
+ <li id="main-20091123_0002">
259
+ 'RE'(action=read, p_action=read): 'wee'(action=read, p_action=read)
260
+ <ul id="main-20091123_0002-files" class="runo-attachment">
261
+ <li id="main-20091123_0002-files-20091123_0001">'roy.png'(action=read, p_action=read)</li>
262
+ </ul>
263
+ <form id="form_main-20091123_0002-replies" method="post" enctype="multipart/form-data" action="/20091123_0002/replies/123.45/update.html">
264
+ <input name="_token" type="hidden" value="#{Runo.token}" />
265
+ <ul id="main-20091123_0002-replies" class="runo-pipco">
266
+ <li id="main-20091123_0002-replies-20091125_0002"><a>'oops.'(action=update, p_action=update)</a></li>
267
+ </ul>
268
+ [replies-update]
269
+ </form>
270
+ 'potato'
271
+ </li>
272
+ </ul>
273
+ _html
274
+ result,
275
+ 'Field#get should be able to handle a partial action'
276
+ )
277
+
278
+ result = @sd.get(
279
+ :conds => {:id => '20091123_0002'},
280
+ '20091123_0002' => {
281
+ 'replies' => {
282
+ :action => :update,
283
+ :conds => {:id => '20091125_0002'},
284
+ },
285
+ }
286
+ )
287
+ assert_equal(
288
+ <<_html,
289
+ <ul id="main" class="runo-pipco">
290
+ <li id="main-20091123_0002">
291
+ 'RE'(action=read, p_action=read): 'wee'(action=read, p_action=read)
292
+ <ul id="main-20091123_0002-files" class="runo-attachment">
293
+ <li id="main-20091123_0002-files-20091123_0001">'roy.png'(action=read, p_action=read)</li>
294
+ </ul>
295
+ <form id="form_main-20091123_0002-replies" method="post" enctype="multipart/form-data" action="/20091123_0002/replies/123.45/update.html">
296
+ <input name="_token" type="hidden" value="#{Runo.token}" />
297
+ <ul id="main-20091123_0002-replies" class="runo-pipco">
298
+ <li id="main-20091123_0002-replies-20091125_0002"><a>'oops.'(action=update, p_action=update)</a></li>
299
+ </ul>
300
+ [replies-update]
301
+ </form>
302
+ 'potato'
303
+ </li>
304
+ </ul>
305
+ _html
306
+ result,
307
+ 'Field#get should be able to handle a partial action'
308
+ )
309
+ end
310
+
311
+ def test_get_partial_forbidden
312
+ Runo.client = 'carl'
313
+ assert_match(
314
+ /\(action=update/,
315
+ @sd.item('20091123_0001', 'files').get(:action => :update)
316
+ )
317
+ assert_match(
318
+ /\(action=update/,
319
+ @sd.item('20091123_0001', 'files', '20091123_0001').get(:action => :update)
320
+ )
321
+
322
+ @sd.instance_variable_set(:@item_object, {}) # remove item('_001')
323
+
324
+ Runo.client = nil
325
+ assert_raise(
326
+ Runo::Error::Forbidden,
327
+ 'Field#get should not show an inner attachment when the parent is forbidden'
328
+ ) {
329
+ @sd.item('20091123_0001', 'files').get(:action => :update)
330
+ }
331
+ assert_raise(
332
+ Runo::Error::Forbidden,
333
+ 'Field#get should not show an inner attachment when the parent is forbidden'
334
+ ) {
335
+ @sd.item('20091123_0001', 'files', '20091123_0001').get(:action => :update)
336
+ }
337
+ end
338
+
339
+ def test_post_partial
340
+ Runo.client = 'don'
341
+ original_val = YAML.load @sd.val.to_yaml
342
+ @sd.update(
343
+ '20091123_0002' => {
344
+ 'replies' => {
345
+ '_0001' => {'reply' => 'yum.'},
346
+ },
347
+ }
348
+ )
349
+ assert_equal(
350
+ original_val,
351
+ @sd.val,
352
+ 'Field#val should not change before the commit'
353
+ )
354
+ @sd.commit
355
+ assert_not_equal(
356
+ original_val,
357
+ @sd.val,
358
+ 'Field#val should change after the commit'
359
+ )
360
+ end
361
+
362
+ def test_post_attachment_forbidden
363
+ Runo.client = nil
364
+ assert_raise(
365
+ Runo::Error::Forbidden,
366
+ 'Field#post to an inner attachment w/o the perm of the parent should be forbidden'
367
+ ) {
368
+ @sd.update(
369
+ '20091123_0002' => {
370
+ 'files' => {
371
+ '_0001' => {'file' => 'evil.jpg'},
372
+ },
373
+ }
374
+ )
375
+ }
376
+ assert_raise(
377
+ Runo::Error::Forbidden,
378
+ 'Field#post to an inner attachment w/o the perm of the parent should be forbidden'
379
+ ) {
380
+ @sd.update(
381
+ '20091123_0002' => {
382
+ 'files' => {
383
+ '20091123_0001' => {'file' => 'evil.png'},
384
+ }
385
+ }
386
+ )
387
+ }
388
+ assert_raise(
389
+ Runo::Error::Forbidden,
390
+ 'Field#post to an inner attachment w/o the perm of the parent should be forbidden'
391
+ ) {
392
+ @sd.item('20091123_0002', 'files', '20091123_0001').update('file' => 'evil.gif')
393
+ }
394
+ end
395
+
396
+ def test_commit_partial
397
+ Runo.client = 'don'
398
+ @sd.update(
399
+ '20091123_0002' => {
400
+ 'replies' => {
401
+ '_0001' => {'reply' => 'yum.'},
402
+ },
403
+ }
404
+ )
405
+ orig_val = @sd.val('20091123_0002', 'replies').dup
406
+
407
+ @sd.commit :temp
408
+ new_val = @sd.val('20091123_0002', 'replies').dup
409
+ assert_equal(
410
+ orig_val.size + 1,
411
+ new_val.size,
412
+ 'Field#val should change after the commit :temp'
413
+ )
414
+
415
+ new_id = new_val.keys.find {|id| new_val[id] == {'_owner' => 'don', 'reply' => 'yum.'} }
416
+ @sd.update(
417
+ '20091123_0002' => {
418
+ 'replies' => {
419
+ new_id => {
420
+ :action => :delete,
421
+ 'reply' => 'yum.',
422
+ },
423
+ },
424
+ }
425
+ )
426
+
427
+ @sd.commit :temp
428
+ new_val = @sd.val('20091123_0002', 'replies').dup
429
+ assert_equal(
430
+ orig_val,
431
+ new_val,
432
+ 'Field#val should change after the commit :temp'
433
+ )
434
+ end
435
+
436
+ def test_post_mixed
437
+ Runo.client = 'don'
438
+
439
+ # create a sub-item on the pending item
440
+ @sd.update(
441
+ '_1234' => {
442
+ '_owner' => 'don',
443
+ 'replies' => {
444
+ '_0001' => {
445
+ '_owner' => 'don',
446
+ 'reply' => 'yum.',
447
+ },
448
+ },
449
+ }
450
+ )
451
+ orig_val = @sd.val('_1234', 'replies').dup
452
+ assert_equal(
453
+ {},
454
+ orig_val,
455
+ 'Field#val should change after the commit :temp'
456
+ )
457
+
458
+ orig_storage = @sd.storage
459
+ @sd.instance_variable_set(:@storage, nil) # pretend persistent
460
+ @sd.commit :temp
461
+ @sd.instance_variable_set(:@storage, orig_storage)
462
+
463
+ new_val = @sd.val('_1234', 'replies').dup
464
+ assert_equal(
465
+ {'_owner' => 'don', 'reply' => 'yum.'},
466
+ new_val.values.first,
467
+ 'Field#val should change after the commit :temp'
468
+ )
469
+
470
+ # delete the sub-item
471
+ new_id = new_val.keys.find {|id| new_val[id] == {'_owner' => 'don', 'reply' => 'yum.'} }
472
+ @sd.update(
473
+ '_1234' => {
474
+ 'replies' => {
475
+ new_id => {
476
+ :action => :delete,
477
+ '_owner' => 'don',
478
+ 'reply' => 'yum.',
479
+ },
480
+ },
481
+ }
482
+ )
483
+ assert_equal(
484
+ :delete,
485
+ @sd.item('_1234', 'replies', new_id).action,
486
+ 'Set::Dynamic#post should not overwrite the action of descendant'
487
+ )
488
+
489
+ orig_storage = @sd.storage
490
+ @sd.instance_variable_set(:@storage, nil) # pretend persistent
491
+ @sd.commit :temp
492
+ @sd.instance_variable_set(:@storage, orig_storage)
493
+
494
+ new_val = @sd.val('_1234', 'replies').dup
495
+ assert_equal(
496
+ {},
497
+ new_val,
498
+ 'Field#val should change after the commit :temp'
499
+ )
500
+
501
+ # create an another sub-item
502
+ @sd.update(
503
+ '_1234' => {
504
+ '_owner' => 'don',
505
+ 'replies' => {
506
+ '_0001' => {
507
+ '_owner' => 'don',
508
+ 'reply' => 'yuck.',
509
+ },
510
+ },
511
+ }
512
+ )
513
+
514
+ orig_storage = @sd.storage
515
+ @sd.instance_variable_set(:@storage, nil) # pretend persistent
516
+ @sd.commit :temp
517
+ @sd.instance_variable_set(:@storage, orig_storage)
518
+
519
+ new_val = @sd.val('_1234', 'replies').dup
520
+ assert_equal(
521
+ {'_owner' => 'don', 'reply' => 'yuck.'},
522
+ new_val.values.first,
523
+ 'Field#val should change after the commit :temp'
524
+ )
525
+ end
526
+
527
+ end