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.
- data/LICENSE +19 -0
- data/README.rdoc +120 -0
- data/bin/runo +35 -0
- data/lib/_error.rb +14 -0
- data/lib/_field.rb +260 -0
- data/lib/_i18n.rb +141 -0
- data/lib/_parser.rb +243 -0
- data/lib/_path.rb +86 -0
- data/lib/_storage/_storage.rb +213 -0
- data/lib/_storage/file.rb +200 -0
- data/lib/_storage/sequel.rb +174 -0
- data/lib/_storage/temp.rb +73 -0
- data/lib/_widget/action_create.rb +23 -0
- data/lib/_widget/action_login.rb +22 -0
- data/lib/_widget/action_signup.rb +16 -0
- data/lib/_widget/action_update.rb +16 -0
- data/lib/_widget/crumb.rb +24 -0
- data/lib/_widget/done.rb +16 -0
- data/lib/_widget/login.rb +25 -0
- data/lib/_widget/me.rb +31 -0
- data/lib/_widget/message.rb +51 -0
- data/lib/_widget/navi.rb +88 -0
- data/lib/_widget/submit.rb +49 -0
- data/lib/_widget/view_ym.rb +77 -0
- data/lib/_workflow/_workflow.rb +89 -0
- data/lib/_workflow/attachment.rb +50 -0
- data/lib/_workflow/blog.rb +28 -0
- data/lib/_workflow/contact.rb +23 -0
- data/lib/_workflow/forum.rb +26 -0
- data/lib/_workflow/register.rb +39 -0
- data/lib/meta/_meta.rb +20 -0
- data/lib/meta/group.rb +19 -0
- data/lib/meta/id.rb +59 -0
- data/lib/meta/owner.rb +21 -0
- data/lib/meta/timestamp.rb +118 -0
- data/lib/runo.rb +396 -0
- data/lib/scalar/checkbox.rb +68 -0
- data/lib/scalar/file.rb +144 -0
- data/lib/scalar/img.rb +112 -0
- data/lib/scalar/password.rb +58 -0
- data/lib/scalar/radio.rb +47 -0
- data/lib/scalar/select.rb +47 -0
- data/lib/scalar/text.rb +38 -0
- data/lib/scalar/textarea.rb +35 -0
- data/lib/scalar/textarea_pre.rb +14 -0
- data/lib/scalar/textarea_wiki.rb +173 -0
- data/lib/set/_set.rb +195 -0
- data/lib/set/dynamic.rb +177 -0
- data/lib/set/static.rb +102 -0
- data/lib/set/static_folder.rb +96 -0
- data/locale/en/index.po +242 -0
- data/locale/index.pot +243 -0
- data/locale/ja/index.po +242 -0
- data/locale/lazy_parser.rb +54 -0
- data/skel/config.ru +27 -0
- data/skel/skin/_users/00000000_frank-avatar.jpg +0 -0
- data/skel/skin/_users/00000000_frank-avatar_small.jpg +0 -0
- data/skel/skin/_users/00000000_frank.yaml +12 -0
- data/skel/skin/_users/00000000_root-avatar.jpg +0 -0
- data/skel/skin/_users/00000000_root-avatar_small.jpg +0 -0
- data/skel/skin/_users/00000000_root.yaml +11 -0
- data/skel/skin/_users/css/users.css +21 -0
- data/skel/skin/_users/css/users.less +25 -0
- data/skel/skin/_users/done.html +42 -0
- data/skel/skin/_users/index.html +46 -0
- data/skel/skin/_users/index.yaml +3 -0
- data/skel/skin/_users/summary.html +40 -0
- data/skel/skin/css/base.css +93 -0
- data/skel/skin/css/base.less +139 -0
- data/skel/skin/css/coax.css +199 -0
- data/skel/skin/css/coax.less +244 -0
- data/skel/skin/examples/blog/20091214_0001.yaml +8 -0
- data/skel/skin/examples/blog/20100630_0001.yaml +8 -0
- data/skel/skin/examples/blog/20100630_0002.yaml +14 -0
- data/skel/skin/examples/blog/20100701_0001.yaml +8 -0
- data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f.jpg +0 -0
- data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f_small.jpg +0 -0
- data/skel/skin/examples/blog/20100701_0002.yaml +19 -0
- data/skel/skin/examples/blog/frank/20100701_0001.yaml +10 -0
- data/skel/skin/examples/blog/frank/index.yaml +4 -0
- data/skel/skin/examples/blog/index.html +51 -0
- data/skel/skin/examples/blog/rss.xml +18 -0
- data/skel/skin/examples/contact/20100701_0001-file.txt +1 -0
- data/skel/skin/examples/contact/20100701_0001.yaml +15 -0
- data/skel/skin/examples/contact/20100701_0002.yaml +8 -0
- data/skel/skin/examples/contact/20100701_0003.yaml +9 -0
- data/skel/skin/examples/contact/index.html +47 -0
- data/skel/skin/examples/contact/js/contact.js +13 -0
- data/skel/skin/examples/contact/summary.html +54 -0
- data/skel/skin/examples/forum/20100701_0001.yaml +41 -0
- data/skel/skin/examples/forum/20100701_0002.yaml +25 -0
- data/skel/skin/examples/forum/index.html +68 -0
- data/skel/skin/examples/forum/summary.html +47 -0
- data/skel/skin/examples/index.html +75 -0
- data/skel/skin/index.html +41 -0
- data/skel/skin/js/base.js +50 -0
- data/t/locale/de/index.po +19 -0
- data/t/locale/en-GB/index.po +25 -0
- data/t/locale/ja/index.po +30 -0
- data/t/skin/_users/00000000_test.yaml +3 -0
- data/t/skin/_users/index.html +13 -0
- data/t/skin/foo/20091120_0001.yaml +7 -0
- data/t/skin/foo/bar/20091120_0001.yaml +5 -0
- data/t/skin/foo/bar/index.yaml +5 -0
- data/t/skin/foo/baz/css/baz.css +1 -0
- data/t/skin/foo/css/foo.css +1 -0
- data/t/skin/foo/index.html +14 -0
- data/t/skin/foo/index.yaml +7 -0
- data/t/skin/foo/not_css/foo.css +1 -0
- data/t/skin/foo/sub-20100306_0001.yaml +3 -0
- data/t/skin/index.yaml +3 -0
- data/t/skin/t_attachment/index.html +13 -0
- data/t/skin/t_contact/done.html +6 -0
- data/t/skin/t_contact/index.html +9 -0
- data/t/skin/t_file/index.html +16 -0
- data/t/skin/t_img/index.html +14 -0
- data/t/skin/t_img/test.jpg +0 -0
- data/t/skin/t_select/index.html +9 -0
- data/t/skin/t_store/index.html +9 -0
- data/t/skin/t_summary/20100326_0001.yaml +3 -0
- data/t/skin/t_summary/create.html +9 -0
- data/t/skin/t_summary/index.html +9 -0
- data/t/skin/t_summary/summary.html +9 -0
- data/t/t.rb +27 -0
- data/t/test_checkbox.rb +273 -0
- data/t/test_field.rb +330 -0
- data/t/test_file.rb +900 -0
- data/t/test_id.rb +215 -0
- data/t/test_img.rb +328 -0
- data/t/test_meta.rb +57 -0
- data/t/test_parser.rb +1266 -0
- data/t/test_password.rb +188 -0
- data/t/test_radio.rb +226 -0
- data/t/test_role.rb +249 -0
- data/t/test_runo.rb +742 -0
- data/t/test_runo_call.rb +1286 -0
- data/t/test_runo_i18n.rb +318 -0
- data/t/test_select.rb +182 -0
- data/t/test_set_complex.rb +527 -0
- data/t/test_set_dynamic.rb +1504 -0
- data/t/test_set_folder.rb +515 -0
- data/t/test_set_permit.rb +246 -0
- data/t/test_set_static.rb +445 -0
- data/t/test_storage.rb +915 -0
- data/t/test_text.rb +125 -0
- data/t/test_textarea.rb +138 -0
- data/t/test_timestamp.rb +473 -0
- data/t/test_workflow.rb +367 -0
- metadata +345 -0
data/t/test_runo_call.rb
ADDED
|
@@ -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
|