##// END OF EJS Templates
add missing fixtures...
Toshi MARUYAMA -
r13529:eacd619f5f0c
parent child
Show More
@@ -1,1507 +1,1508
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../../test_helper', __FILE__)
21 21
22 22 class ApplicationHelperTest < ActionView::TestCase
23 23 include Redmine::I18n
24 24 include ERB::Util
25 25 include Rails.application.routes.url_helpers
26 26
27 27 fixtures :projects, :roles, :enabled_modules, :users,
28 :email_addresses,
28 29 :repositories, :changesets,
29 30 :projects_trackers,
30 31 :trackers, :issue_statuses, :issues, :versions, :documents,
31 32 :wikis, :wiki_pages, :wiki_contents,
32 33 :boards, :messages, :news,
33 34 :attachments, :enumerations
34 35
35 36 def setup
36 37 super
37 38 set_tmp_attachments_directory
38 39 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82".force_encoding('UTF-8')
39 40 end
40 41
41 42 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
42 43 User.current = User.find_by_login('admin')
43 44
44 45 @project = Issue.first.project # Used by helper
45 46 response = link_to_if_authorized('By controller/actionr',
46 47 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
47 48 assert_match /href/, response
48 49 end
49 50
50 51 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
51 52 User.current = User.find_by_login('dlopper')
52 53 @project = Project.find('private-child')
53 54 issue = @project.issues.first
54 55 assert !issue.visible?
55 56
56 57 response = link_to_if_authorized('Never displayed',
57 58 {:controller => 'issues', :action => 'show', :id => issue})
58 59 assert_nil response
59 60 end
60 61
61 62 def test_auto_links
62 63 to_test = {
63 64 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
64 65 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
65 66 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
66 67 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
67 68 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
68 69 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
69 70 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
70 71 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
71 72 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
72 73 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
73 74 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
74 75 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
75 76 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
76 77 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
77 78 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
78 79 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
79 80 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
80 81 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
81 82 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
82 83 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
83 84 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
84 85 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
85 86 # two exclamation marks
86 87 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
87 88 # escaping
88 89 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
89 90 # wrap in angle brackets
90 91 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
91 92 # invalid urls
92 93 'http://' => 'http://',
93 94 'www.' => 'www.',
94 95 'test-www.bar.com' => 'test-www.bar.com',
95 96 }
96 97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
97 98 end
98 99
99 100 def test_auto_links_with_non_ascii_characters
100 101 to_test = {
101 102 "http://foo.bar/#{@russian_test}" =>
102 103 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
103 104 }
104 105 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
105 106 end
106 107
107 108 def test_auto_mailto
108 109 to_test = {
109 110 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
110 111 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
111 112 }
112 113 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
113 114 end
114 115
115 116 def test_inline_images
116 117 to_test = {
117 118 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
118 119 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
119 120 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
120 121 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
121 122 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
122 123 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
123 124 }
124 125 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
125 126 end
126 127
127 128 def test_inline_images_inside_tags
128 129 raw = <<-RAW
129 130 h1. !foo.png! Heading
130 131
131 132 Centered image:
132 133
133 134 p=. !bar.gif!
134 135 RAW
135 136
136 137 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
137 138 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
138 139 end
139 140
140 141 def test_attached_images
141 142 to_test = {
142 143 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
143 144 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
144 145 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
145 146 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
146 147 # link image
147 148 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
148 149 }
149 150 attachments = Attachment.all
150 151 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
151 152 end
152 153
153 154 def test_attached_images_filename_extension
154 155 set_tmp_attachments_directory
155 156 a1 = Attachment.new(
156 157 :container => Issue.find(1),
157 158 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
158 159 :author => User.find(1))
159 160 assert a1.save
160 161 assert_equal "testtest.JPG", a1.filename
161 162 assert_equal "image/jpeg", a1.content_type
162 163 assert a1.image?
163 164
164 165 a2 = Attachment.new(
165 166 :container => Issue.find(1),
166 167 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
167 168 :author => User.find(1))
168 169 assert a2.save
169 170 assert_equal "testtest.jpeg", a2.filename
170 171 assert_equal "image/jpeg", a2.content_type
171 172 assert a2.image?
172 173
173 174 a3 = Attachment.new(
174 175 :container => Issue.find(1),
175 176 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
176 177 :author => User.find(1))
177 178 assert a3.save
178 179 assert_equal "testtest.JPE", a3.filename
179 180 assert_equal "image/jpeg", a3.content_type
180 181 assert a3.image?
181 182
182 183 a4 = Attachment.new(
183 184 :container => Issue.find(1),
184 185 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
185 186 :author => User.find(1))
186 187 assert a4.save
187 188 assert_equal "Testtest.BMP", a4.filename
188 189 assert_equal "image/x-ms-bmp", a4.content_type
189 190 assert a4.image?
190 191
191 192 to_test = {
192 193 'Inline image: !testtest.jpg!' =>
193 194 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
194 195 'Inline image: !testtest.jpeg!' =>
195 196 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
196 197 'Inline image: !testtest.jpe!' =>
197 198 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
198 199 'Inline image: !testtest.bmp!' =>
199 200 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
200 201 }
201 202
202 203 attachments = [a1, a2, a3, a4]
203 204 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
204 205 end
205 206
206 207 def test_attached_images_should_read_later
207 208 set_fixtures_attachments_directory
208 209 a1 = Attachment.find(16)
209 210 assert_equal "testfile.png", a1.filename
210 211 assert a1.readable?
211 212 assert (! a1.visible?(User.anonymous))
212 213 assert a1.visible?(User.find(2))
213 214 a2 = Attachment.find(17)
214 215 assert_equal "testfile.PNG", a2.filename
215 216 assert a2.readable?
216 217 assert (! a2.visible?(User.anonymous))
217 218 assert a2.visible?(User.find(2))
218 219 assert a1.created_on < a2.created_on
219 220
220 221 to_test = {
221 222 'Inline image: !testfile.png!' =>
222 223 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
223 224 'Inline image: !Testfile.PNG!' =>
224 225 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
225 226 }
226 227 attachments = [a1, a2]
227 228 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
228 229 set_tmp_attachments_directory
229 230 end
230 231
231 232 def test_textile_external_links
232 233 to_test = {
233 234 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
234 235 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
235 236 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
236 237 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
237 238 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
238 239 # no multiline link text
239 240 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
240 241 # mailto link
241 242 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
242 243 # two exclamation marks
243 244 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
244 245 # escaping
245 246 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
246 247 }
247 248 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
248 249 end
249 250
250 251 def test_textile_external_links_with_non_ascii_characters
251 252 to_test = {
252 253 %|This is a "link":http://foo.bar/#{@russian_test}| =>
253 254 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
254 255 }
255 256 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
256 257 end
257 258
258 259 def test_redmine_links
259 260 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
260 261 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
261 262 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
262 263 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
263 264 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
264 265 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
265 266
266 267 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
267 268 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
268 269 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
269 270 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
270 271
271 272 changeset_link2 = link_to('691322a8eb01e11fd7',
272 273 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
273 274 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
274 275
275 276 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
276 277 :class => 'document')
277 278
278 279 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
279 280 :class => 'version')
280 281
281 282 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
282 283
283 284 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
284 285
285 286 news_url = {:controller => 'news', :action => 'show', :id => 1}
286 287
287 288 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
288 289
289 290 source_url = '/projects/ecookbook/repository/entry/some/file'
290 291 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
291 292 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
292 293 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
293 294 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
294 295
295 296 export_url = '/projects/ecookbook/repository/raw/some/file'
296 297 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
297 298 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
298 299 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
299 300 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
300 301
301 302 to_test = {
302 303 # tickets
303 304 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
304 305 # ticket notes
305 306 '#3-14' => note_link,
306 307 '#3#note-14' => note_link2,
307 308 # should not ignore leading zero
308 309 '#03' => '#03',
309 310 # changesets
310 311 'r1' => revision_link,
311 312 'r1.' => "#{revision_link}.",
312 313 'r1, r2' => "#{revision_link}, #{revision_link2}",
313 314 'r1,r2' => "#{revision_link},#{revision_link2}",
314 315 'commit:691322a8eb01e11fd7' => changeset_link2,
315 316 # documents
316 317 'document#1' => document_link,
317 318 'document:"Test document"' => document_link,
318 319 # versions
319 320 'version#2' => version_link,
320 321 'version:1.0' => version_link,
321 322 'version:"1.0"' => version_link,
322 323 # source
323 324 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
324 325 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
325 326 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
326 327 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
327 328 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
328 329 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
329 330 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
330 331 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
331 332 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
332 333 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
333 334 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
334 335 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
335 336 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
336 337 # export
337 338 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
338 339 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
339 340 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
340 341 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
341 342 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
342 343 # forum
343 344 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
344 345 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
345 346 # message
346 347 'message#4' => link_to('Post 2', message_url, :class => 'message'),
347 348 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
348 349 # news
349 350 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
350 351 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
351 352 # project
352 353 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
353 354 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
354 355 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
355 356 # not found
356 357 '#0123456789' => '#0123456789',
357 358 # invalid expressions
358 359 'source:' => 'source:',
359 360 # url hash
360 361 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
361 362 }
362 363 @project = Project.find(1)
363 364 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
364 365 end
365 366
366 367 def test_should_not_parse_redmine_links_inside_link
367 368 raw = "r1 should not be parsed in http://example.com/url-r1/"
368 369 assert_match %r{<p><a class="changeset".*>r1</a> should not be parsed in <a class="external" href="http://example.com/url-r1/">http://example.com/url-r1/</a></p>},
369 370 textilizable(raw, :project => Project.find(1))
370 371 end
371 372
372 373 def test_redmine_links_with_a_different_project_before_current_project
373 374 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
374 375 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
375 376 @project = Project.find(3)
376 377 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
377 378 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
378 379 assert_equal "<p>#{result1} #{result2}</p>",
379 380 textilizable("ecookbook:version:1.4.4 version:1.4.4")
380 381 end
381 382
382 383 def test_escaped_redmine_links_should_not_be_parsed
383 384 to_test = [
384 385 '#3.',
385 386 '#3-14.',
386 387 '#3#-note14.',
387 388 'r1',
388 389 'document#1',
389 390 'document:"Test document"',
390 391 'version#2',
391 392 'version:1.0',
392 393 'version:"1.0"',
393 394 'source:/some/file'
394 395 ]
395 396 @project = Project.find(1)
396 397 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
397 398 end
398 399
399 400 def test_cross_project_redmine_links
400 401 source_link = link_to('ecookbook:source:/some/file',
401 402 {:controller => 'repositories', :action => 'entry',
402 403 :id => 'ecookbook', :path => ['some', 'file']},
403 404 :class => 'source')
404 405 changeset_link = link_to('ecookbook:r2',
405 406 {:controller => 'repositories', :action => 'revision',
406 407 :id => 'ecookbook', :rev => 2},
407 408 :class => 'changeset',
408 409 :title => 'This commit fixes #1, #2 and references #1 & #3')
409 410 to_test = {
410 411 # documents
411 412 'document:"Test document"' => 'document:"Test document"',
412 413 'ecookbook:document:"Test document"' =>
413 414 link_to("Test document", "/documents/1", :class => "document"),
414 415 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
415 416 # versions
416 417 'version:"1.0"' => 'version:"1.0"',
417 418 'ecookbook:version:"1.0"' =>
418 419 link_to("1.0", "/versions/2", :class => "version"),
419 420 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
420 421 # changeset
421 422 'r2' => 'r2',
422 423 'ecookbook:r2' => changeset_link,
423 424 'invalid:r2' => 'invalid:r2',
424 425 # source
425 426 'source:/some/file' => 'source:/some/file',
426 427 'ecookbook:source:/some/file' => source_link,
427 428 'invalid:source:/some/file' => 'invalid:source:/some/file',
428 429 }
429 430 @project = Project.find(3)
430 431 to_test.each do |text, result|
431 432 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
432 433 end
433 434 end
434 435
435 436 def test_redmine_links_by_name_should_work_with_html_escaped_characters
436 437 v = Version.generate!(:name => "Test & Show.txt", :project_id => 1)
437 438 link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version")
438 439
439 440 @project = v.project
440 441 assert_equal "<p>#{link}</p>", textilizable('version:"Test & Show.txt"')
441 442 end
442 443
443 444 def test_link_to_issue_subject
444 445 issue = Issue.generate!(:subject => "01234567890123456789")
445 446 str = link_to_issue(issue, :truncate => 10)
446 447 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
447 448 assert_equal "#{result}: 0123456...", str
448 449
449 450 issue = Issue.generate!(:subject => "<&>")
450 451 str = link_to_issue(issue)
451 452 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
452 453 assert_equal "#{result}: &lt;&amp;&gt;", str
453 454
454 455 issue = Issue.generate!(:subject => "<&>0123456789012345")
455 456 str = link_to_issue(issue, :truncate => 10)
456 457 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
457 458 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
458 459 end
459 460
460 461 def test_link_to_issue_title
461 462 long_str = "0123456789" * 5
462 463
463 464 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
464 465 str = link_to_issue(issue, :subject => false)
465 466 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
466 467 :class => issue.css_classes,
467 468 :title => "#{long_str}0123456...")
468 469 assert_equal result, str
469 470
470 471 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
471 472 str = link_to_issue(issue, :subject => false)
472 473 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
473 474 :class => issue.css_classes,
474 475 :title => "<&>#{long_str}0123...")
475 476 assert_equal result, str
476 477 end
477 478
478 479 def test_multiple_repositories_redmine_links
479 480 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
480 481 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
481 482 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
482 483 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
483 484
484 485 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
485 486 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
486 487 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
487 488 :class => 'changeset', :title => '')
488 489 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
489 490 :class => 'changeset', :title => '')
490 491
491 492 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
492 493 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
493 494
494 495 to_test = {
495 496 'r2' => changeset_link,
496 497 'svn_repo-1|r123' => svn_changeset_link,
497 498 'invalid|r123' => 'invalid|r123',
498 499 'commit:hg1|abcd' => hg_changeset_link,
499 500 'commit:invalid|abcd' => 'commit:invalid|abcd',
500 501 # source
501 502 'source:some/file' => source_link,
502 503 'source:hg1|some/file' => hg_source_link,
503 504 'source:invalid|some/file' => 'source:invalid|some/file',
504 505 }
505 506
506 507 @project = Project.find(1)
507 508 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
508 509 end
509 510
510 511 def test_cross_project_multiple_repositories_redmine_links
511 512 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
512 513 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
513 514 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
514 515 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
515 516
516 517 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
517 518 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
518 519 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
519 520 :class => 'changeset', :title => '')
520 521 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
521 522 :class => 'changeset', :title => '')
522 523
523 524 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
524 525 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
525 526
526 527 to_test = {
527 528 'ecookbook:r2' => changeset_link,
528 529 'ecookbook:svn1|r123' => svn_changeset_link,
529 530 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
530 531 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
531 532 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
532 533 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
533 534 # source
534 535 'ecookbook:source:some/file' => source_link,
535 536 'ecookbook:source:hg1|some/file' => hg_source_link,
536 537 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
537 538 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
538 539 }
539 540
540 541 @project = Project.find(3)
541 542 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
542 543 end
543 544
544 545 def test_redmine_links_git_commit
545 546 changeset_link = link_to('abcd',
546 547 {
547 548 :controller => 'repositories',
548 549 :action => 'revision',
549 550 :id => 'subproject1',
550 551 :rev => 'abcd',
551 552 },
552 553 :class => 'changeset', :title => 'test commit')
553 554 to_test = {
554 555 'commit:abcd' => changeset_link,
555 556 }
556 557 @project = Project.find(3)
557 558 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
558 559 assert r
559 560 c = Changeset.new(:repository => r,
560 561 :committed_on => Time.now,
561 562 :revision => 'abcd',
562 563 :scmid => 'abcd',
563 564 :comments => 'test commit')
564 565 assert( c.save )
565 566 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
566 567 end
567 568
568 569 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
569 570 def test_redmine_links_darcs_commit
570 571 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
571 572 {
572 573 :controller => 'repositories',
573 574 :action => 'revision',
574 575 :id => 'subproject1',
575 576 :rev => '123',
576 577 },
577 578 :class => 'changeset', :title => 'test commit')
578 579 to_test = {
579 580 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
580 581 }
581 582 @project = Project.find(3)
582 583 r = Repository::Darcs.create!(
583 584 :project => @project, :url => '/tmp/test/darcs',
584 585 :log_encoding => 'UTF-8')
585 586 assert r
586 587 c = Changeset.new(:repository => r,
587 588 :committed_on => Time.now,
588 589 :revision => '123',
589 590 :scmid => '20080308225258-98289-abcd456efg.gz',
590 591 :comments => 'test commit')
591 592 assert( c.save )
592 593 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
593 594 end
594 595
595 596 def test_redmine_links_mercurial_commit
596 597 changeset_link_rev = link_to('r123',
597 598 {
598 599 :controller => 'repositories',
599 600 :action => 'revision',
600 601 :id => 'subproject1',
601 602 :rev => '123' ,
602 603 },
603 604 :class => 'changeset', :title => 'test commit')
604 605 changeset_link_commit = link_to('abcd',
605 606 {
606 607 :controller => 'repositories',
607 608 :action => 'revision',
608 609 :id => 'subproject1',
609 610 :rev => 'abcd' ,
610 611 },
611 612 :class => 'changeset', :title => 'test commit')
612 613 to_test = {
613 614 'r123' => changeset_link_rev,
614 615 'commit:abcd' => changeset_link_commit,
615 616 }
616 617 @project = Project.find(3)
617 618 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
618 619 assert r
619 620 c = Changeset.new(:repository => r,
620 621 :committed_on => Time.now,
621 622 :revision => '123',
622 623 :scmid => 'abcd',
623 624 :comments => 'test commit')
624 625 assert( c.save )
625 626 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
626 627 end
627 628
628 629 def test_attachment_links
629 630 text = 'attachment:error281.txt'
630 631 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
631 632 :class => "attachment")
632 633 assert_equal "<p>#{result}</p>",
633 634 textilizable(text,
634 635 :attachments => Issue.find(3).attachments),
635 636 "#{text} failed"
636 637 end
637 638
638 639 def test_attachment_link_should_link_to_latest_attachment
639 640 set_tmp_attachments_directory
640 641 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
641 642 a2 = Attachment.generate!(:filename => "test.txt")
642 643 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
643 644 :class => "attachment")
644 645 assert_equal "<p>#{result}</p>",
645 646 textilizable('attachment:test.txt', :attachments => [a1, a2])
646 647 end
647 648
648 649 def test_wiki_links
649 650 russian_eacape = CGI.escape(@russian_test)
650 651 to_test = {
651 652 '[[CookBook documentation]]' =>
652 653 link_to("CookBook documentation",
653 654 "/projects/ecookbook/wiki/CookBook_documentation",
654 655 :class => "wiki-page"),
655 656 '[[Another page|Page]]' =>
656 657 link_to("Page",
657 658 "/projects/ecookbook/wiki/Another_page",
658 659 :class => "wiki-page"),
659 660 # title content should be formatted
660 661 '[[Another page|With _styled_ *title*]]' =>
661 662 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
662 663 "/projects/ecookbook/wiki/Another_page",
663 664 :class => "wiki-page"),
664 665 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
665 666 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
666 667 "/projects/ecookbook/wiki/Another_page",
667 668 :class => "wiki-page"),
668 669 # link with anchor
669 670 '[[CookBook documentation#One-section]]' =>
670 671 link_to("CookBook documentation",
671 672 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
672 673 :class => "wiki-page"),
673 674 '[[Another page#anchor|Page]]' =>
674 675 link_to("Page",
675 676 "/projects/ecookbook/wiki/Another_page#anchor",
676 677 :class => "wiki-page"),
677 678 # UTF8 anchor
678 679 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
679 680 link_to(@russian_test,
680 681 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
681 682 :class => "wiki-page"),
682 683 # page that doesn't exist
683 684 '[[Unknown page]]' =>
684 685 link_to("Unknown page",
685 686 "/projects/ecookbook/wiki/Unknown_page",
686 687 :class => "wiki-page new"),
687 688 '[[Unknown page|404]]' =>
688 689 link_to("404",
689 690 "/projects/ecookbook/wiki/Unknown_page",
690 691 :class => "wiki-page new"),
691 692 # link to another project wiki
692 693 '[[onlinestore:]]' =>
693 694 link_to("onlinestore",
694 695 "/projects/onlinestore/wiki",
695 696 :class => "wiki-page"),
696 697 '[[onlinestore:|Wiki]]' =>
697 698 link_to("Wiki",
698 699 "/projects/onlinestore/wiki",
699 700 :class => "wiki-page"),
700 701 '[[onlinestore:Start page]]' =>
701 702 link_to("Start page",
702 703 "/projects/onlinestore/wiki/Start_page",
703 704 :class => "wiki-page"),
704 705 '[[onlinestore:Start page|Text]]' =>
705 706 link_to("Text",
706 707 "/projects/onlinestore/wiki/Start_page",
707 708 :class => "wiki-page"),
708 709 '[[onlinestore:Unknown page]]' =>
709 710 link_to("Unknown page",
710 711 "/projects/onlinestore/wiki/Unknown_page",
711 712 :class => "wiki-page new"),
712 713 # struck through link
713 714 '-[[Another page|Page]]-' =>
714 715 "<del>".html_safe +
715 716 link_to("Page",
716 717 "/projects/ecookbook/wiki/Another_page",
717 718 :class => "wiki-page").html_safe +
718 719 "</del>".html_safe,
719 720 '-[[Another page|Page]] link-' =>
720 721 "<del>".html_safe +
721 722 link_to("Page",
722 723 "/projects/ecookbook/wiki/Another_page",
723 724 :class => "wiki-page").html_safe +
724 725 " link</del>".html_safe,
725 726 # escaping
726 727 '![[Another page|Page]]' => '[[Another page|Page]]',
727 728 # project does not exist
728 729 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
729 730 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
730 731 }
731 732 @project = Project.find(1)
732 733 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
733 734 end
734 735
735 736 def test_wiki_links_within_local_file_generation_context
736 737 to_test = {
737 738 # link to a page
738 739 '[[CookBook documentation]]' =>
739 740 link_to("CookBook documentation", "CookBook_documentation.html",
740 741 :class => "wiki-page"),
741 742 '[[CookBook documentation|documentation]]' =>
742 743 link_to("documentation", "CookBook_documentation.html",
743 744 :class => "wiki-page"),
744 745 '[[CookBook documentation#One-section]]' =>
745 746 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
746 747 :class => "wiki-page"),
747 748 '[[CookBook documentation#One-section|documentation]]' =>
748 749 link_to("documentation", "CookBook_documentation.html#One-section",
749 750 :class => "wiki-page"),
750 751 # page that doesn't exist
751 752 '[[Unknown page]]' =>
752 753 link_to("Unknown page", "Unknown_page.html",
753 754 :class => "wiki-page new"),
754 755 '[[Unknown page|404]]' =>
755 756 link_to("404", "Unknown_page.html",
756 757 :class => "wiki-page new"),
757 758 '[[Unknown page#anchor]]' =>
758 759 link_to("Unknown page", "Unknown_page.html#anchor",
759 760 :class => "wiki-page new"),
760 761 '[[Unknown page#anchor|404]]' =>
761 762 link_to("404", "Unknown_page.html#anchor",
762 763 :class => "wiki-page new"),
763 764 }
764 765 @project = Project.find(1)
765 766 to_test.each do |text, result|
766 767 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
767 768 end
768 769 end
769 770
770 771 def test_wiki_links_within_wiki_page_context
771 772 page = WikiPage.find_by_title('Another_page' )
772 773 to_test = {
773 774 '[[CookBook documentation]]' =>
774 775 link_to("CookBook documentation",
775 776 "/projects/ecookbook/wiki/CookBook_documentation",
776 777 :class => "wiki-page"),
777 778 '[[CookBook documentation|documentation]]' =>
778 779 link_to("documentation",
779 780 "/projects/ecookbook/wiki/CookBook_documentation",
780 781 :class => "wiki-page"),
781 782 '[[CookBook documentation#One-section]]' =>
782 783 link_to("CookBook documentation",
783 784 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
784 785 :class => "wiki-page"),
785 786 '[[CookBook documentation#One-section|documentation]]' =>
786 787 link_to("documentation",
787 788 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
788 789 :class => "wiki-page"),
789 790 # link to the current page
790 791 '[[Another page]]' =>
791 792 link_to("Another page",
792 793 "/projects/ecookbook/wiki/Another_page",
793 794 :class => "wiki-page"),
794 795 '[[Another page|Page]]' =>
795 796 link_to("Page",
796 797 "/projects/ecookbook/wiki/Another_page",
797 798 :class => "wiki-page"),
798 799 '[[Another page#anchor]]' =>
799 800 link_to("Another page",
800 801 "#anchor",
801 802 :class => "wiki-page"),
802 803 '[[Another page#anchor|Page]]' =>
803 804 link_to("Page",
804 805 "#anchor",
805 806 :class => "wiki-page"),
806 807 # page that doesn't exist
807 808 '[[Unknown page]]' =>
808 809 link_to("Unknown page",
809 810 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
810 811 :class => "wiki-page new"),
811 812 '[[Unknown page|404]]' =>
812 813 link_to("404",
813 814 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
814 815 :class => "wiki-page new"),
815 816 '[[Unknown page#anchor]]' =>
816 817 link_to("Unknown page",
817 818 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
818 819 :class => "wiki-page new"),
819 820 '[[Unknown page#anchor|404]]' =>
820 821 link_to("404",
821 822 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
822 823 :class => "wiki-page new"),
823 824 }
824 825 @project = Project.find(1)
825 826 to_test.each do |text, result|
826 827 assert_equal "<p>#{result}</p>",
827 828 textilizable(WikiContent.new( :text => text, :page => page ), :text)
828 829 end
829 830 end
830 831
831 832 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
832 833 to_test = {
833 834 # link to a page
834 835 '[[CookBook documentation]]' =>
835 836 link_to("CookBook documentation",
836 837 "#CookBook_documentation",
837 838 :class => "wiki-page"),
838 839 '[[CookBook documentation|documentation]]' =>
839 840 link_to("documentation",
840 841 "#CookBook_documentation",
841 842 :class => "wiki-page"),
842 843 '[[CookBook documentation#One-section]]' =>
843 844 link_to("CookBook documentation",
844 845 "#CookBook_documentation_One-section",
845 846 :class => "wiki-page"),
846 847 '[[CookBook documentation#One-section|documentation]]' =>
847 848 link_to("documentation",
848 849 "#CookBook_documentation_One-section",
849 850 :class => "wiki-page"),
850 851 # page that doesn't exist
851 852 '[[Unknown page]]' =>
852 853 link_to("Unknown page",
853 854 "#Unknown_page",
854 855 :class => "wiki-page new"),
855 856 '[[Unknown page|404]]' =>
856 857 link_to("404",
857 858 "#Unknown_page",
858 859 :class => "wiki-page new"),
859 860 '[[Unknown page#anchor]]' =>
860 861 link_to("Unknown page",
861 862 "#Unknown_page_anchor",
862 863 :class => "wiki-page new"),
863 864 '[[Unknown page#anchor|404]]' =>
864 865 link_to("404",
865 866 "#Unknown_page_anchor",
866 867 :class => "wiki-page new"),
867 868 }
868 869 @project = Project.find(1)
869 870 to_test.each do |text, result|
870 871 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
871 872 end
872 873 end
873 874
874 875 def test_html_tags
875 876 to_test = {
876 877 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
877 878 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
878 879 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
879 880 # do not escape pre/code tags
880 881 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
881 882 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
882 883 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
883 884 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
884 885 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
885 886 # remove attributes except class
886 887 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
887 888 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
888 889 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
889 890 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
890 891 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
891 892 # xss
892 893 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
893 894 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
894 895 }
895 896 to_test.each { |text, result| assert_equal result, textilizable(text) }
896 897 end
897 898
898 899 def test_allowed_html_tags
899 900 to_test = {
900 901 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
901 902 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
902 903 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
903 904 }
904 905 to_test.each { |text, result| assert_equal result, textilizable(text) }
905 906 end
906 907
907 908 def test_pre_tags
908 909 raw = <<-RAW
909 910 Before
910 911
911 912 <pre>
912 913 <prepared-statement-cache-size>32</prepared-statement-cache-size>
913 914 </pre>
914 915
915 916 After
916 917 RAW
917 918
918 919 expected = <<-EXPECTED
919 920 <p>Before</p>
920 921 <pre>
921 922 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
922 923 </pre>
923 924 <p>After</p>
924 925 EXPECTED
925 926
926 927 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
927 928 end
928 929
929 930 def test_pre_content_should_not_parse_wiki_and_redmine_links
930 931 raw = <<-RAW
931 932 [[CookBook documentation]]
932 933
933 934 #1
934 935
935 936 <pre>
936 937 [[CookBook documentation]]
937 938
938 939 #1
939 940 </pre>
940 941 RAW
941 942
942 943 result1 = link_to("CookBook documentation",
943 944 "/projects/ecookbook/wiki/CookBook_documentation",
944 945 :class => "wiki-page")
945 946 result2 = link_to('#1',
946 947 "/issues/1",
947 948 :class => Issue.find(1).css_classes,
948 949 :title => "Cannot print recipes (New)")
949 950
950 951 expected = <<-EXPECTED
951 952 <p>#{result1}</p>
952 953 <p>#{result2}</p>
953 954 <pre>
954 955 [[CookBook documentation]]
955 956
956 957 #1
957 958 </pre>
958 959 EXPECTED
959 960
960 961 @project = Project.find(1)
961 962 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
962 963 end
963 964
964 965 def test_non_closing_pre_blocks_should_be_closed
965 966 raw = <<-RAW
966 967 <pre><code>
967 968 RAW
968 969
969 970 expected = <<-EXPECTED
970 971 <pre><code>
971 972 </code></pre>
972 973 EXPECTED
973 974
974 975 @project = Project.find(1)
975 976 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
976 977 end
977 978
978 979 def test_syntax_highlight
979 980 raw = <<-RAW
980 981 <pre><code class="ruby">
981 982 # Some ruby code here
982 983 </code></pre>
983 984 RAW
984 985
985 986 expected = <<-EXPECTED
986 987 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
987 988 </code></pre>
988 989 EXPECTED
989 990
990 991 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
991 992 end
992 993
993 994 def test_to_path_param
994 995 assert_equal 'test1/test2', to_path_param('test1/test2')
995 996 assert_equal 'test1/test2', to_path_param('/test1/test2/')
996 997 assert_equal 'test1/test2', to_path_param('//test1/test2/')
997 998 assert_equal nil, to_path_param('/')
998 999 end
999 1000
1000 1001 def test_wiki_links_in_tables
1001 1002 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
1002 1003 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
1003 1004 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1004 1005 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1005 1006 result = "<tr><td>#{link1}</td>" +
1006 1007 "<td>#{link2}</td>" +
1007 1008 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1008 1009 @project = Project.find(1)
1009 1010 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1010 1011 end
1011 1012
1012 1013 def test_text_formatting
1013 1014 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1014 1015 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1015 1016 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1016 1017 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
1017 1018 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
1018 1019 }
1019 1020 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1020 1021 end
1021 1022
1022 1023 def test_wiki_horizontal_rule
1023 1024 assert_equal '<hr />', textilizable('---')
1024 1025 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1025 1026 end
1026 1027
1027 1028 def test_footnotes
1028 1029 raw = <<-RAW
1029 1030 This is some text[1].
1030 1031
1031 1032 fn1. This is the foot note
1032 1033 RAW
1033 1034
1034 1035 expected = <<-EXPECTED
1035 1036 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1036 1037 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1037 1038 EXPECTED
1038 1039
1039 1040 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1040 1041 end
1041 1042
1042 1043 def test_headings
1043 1044 raw = 'h1. Some heading'
1044 1045 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1045 1046
1046 1047 assert_equal expected, textilizable(raw)
1047 1048 end
1048 1049
1049 1050 def test_headings_with_special_chars
1050 1051 # This test makes sure that the generated anchor names match the expected
1051 1052 # ones even if the heading text contains unconventional characters
1052 1053 raw = 'h1. Some heading related to version 0.5'
1053 1054 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1054 1055 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1055 1056
1056 1057 assert_equal expected, textilizable(raw)
1057 1058 end
1058 1059
1059 1060 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1060 1061 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1061 1062 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1062 1063
1063 1064 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1064 1065
1065 1066 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1066 1067 end
1067 1068
1068 1069 def test_table_of_content
1069 1070 raw = <<-RAW
1070 1071 {{toc}}
1071 1072
1072 1073 h1. Title
1073 1074
1074 1075 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1075 1076
1076 1077 h2. Subtitle with a [[Wiki]] link
1077 1078
1078 1079 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1079 1080
1080 1081 h2. Subtitle with [[Wiki|another Wiki]] link
1081 1082
1082 1083 h2. Subtitle with %{color:red}red text%
1083 1084
1084 1085 <pre>
1085 1086 some code
1086 1087 </pre>
1087 1088
1088 1089 h3. Subtitle with *some* _modifiers_
1089 1090
1090 1091 h3. Subtitle with @inline code@
1091 1092
1092 1093 h1. Another title
1093 1094
1094 1095 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1095 1096
1096 1097 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1097 1098
1098 1099 RAW
1099 1100
1100 1101 expected = '<ul class="toc">' +
1101 1102 '<li><a href="#Title">Title</a>' +
1102 1103 '<ul>' +
1103 1104 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1104 1105 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1105 1106 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1106 1107 '<ul>' +
1107 1108 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1108 1109 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1109 1110 '</ul>' +
1110 1111 '</li>' +
1111 1112 '</ul>' +
1112 1113 '</li>' +
1113 1114 '<li><a href="#Another-title">Another title</a>' +
1114 1115 '<ul>' +
1115 1116 '<li>' +
1116 1117 '<ul>' +
1117 1118 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1118 1119 '</ul>' +
1119 1120 '</li>' +
1120 1121 '<li><a href="#Project-Name">Project Name</a></li>' +
1121 1122 '</ul>' +
1122 1123 '</li>' +
1123 1124 '</ul>'
1124 1125
1125 1126 @project = Project.find(1)
1126 1127 assert textilizable(raw).gsub("\n", "").include?(expected)
1127 1128 end
1128 1129
1129 1130 def test_table_of_content_should_generate_unique_anchors
1130 1131 raw = <<-RAW
1131 1132 {{toc}}
1132 1133
1133 1134 h1. Title
1134 1135
1135 1136 h2. Subtitle
1136 1137
1137 1138 h2. Subtitle
1138 1139 RAW
1139 1140
1140 1141 expected = '<ul class="toc">' +
1141 1142 '<li><a href="#Title">Title</a>' +
1142 1143 '<ul>' +
1143 1144 '<li><a href="#Subtitle">Subtitle</a></li>' +
1144 1145 '<li><a href="#Subtitle-2">Subtitle</a></li>'
1145 1146 '</ul>'
1146 1147 '</li>' +
1147 1148 '</ul>'
1148 1149
1149 1150 @project = Project.find(1)
1150 1151 result = textilizable(raw).gsub("\n", "")
1151 1152 assert_include expected, result
1152 1153 assert_include '<a name="Subtitle">', result
1153 1154 assert_include '<a name="Subtitle-2">', result
1154 1155 end
1155 1156
1156 1157 def test_table_of_content_should_contain_included_page_headings
1157 1158 raw = <<-RAW
1158 1159 {{toc}}
1159 1160
1160 1161 h1. Included
1161 1162
1162 1163 {{include(Child_1)}}
1163 1164 RAW
1164 1165
1165 1166 expected = '<ul class="toc">' +
1166 1167 '<li><a href="#Included">Included</a></li>' +
1167 1168 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1168 1169 '</ul>'
1169 1170
1170 1171 @project = Project.find(1)
1171 1172 assert textilizable(raw).gsub("\n", "").include?(expected)
1172 1173 end
1173 1174
1174 1175 def test_toc_with_textile_formatting_should_be_parsed
1175 1176 with_settings :text_formatting => 'textile' do
1176 1177 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1177 1178 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1178 1179 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1179 1180 end
1180 1181 end
1181 1182
1182 1183 if Object.const_defined?(:Redcarpet)
1183 1184 def test_toc_with_markdown_formatting_should_be_parsed
1184 1185 with_settings :text_formatting => 'markdown' do
1185 1186 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1186 1187 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1187 1188 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1188 1189 end
1189 1190 end
1190 1191 end
1191 1192
1192 1193 def test_section_edit_links
1193 1194 raw = <<-RAW
1194 1195 h1. Title
1195 1196
1196 1197 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1197 1198
1198 1199 h2. Subtitle with a [[Wiki]] link
1199 1200
1200 1201 h2. Subtitle with *some* _modifiers_
1201 1202
1202 1203 h2. Subtitle with @inline code@
1203 1204
1204 1205 <pre>
1205 1206 some code
1206 1207
1207 1208 h2. heading inside pre
1208 1209
1209 1210 <h2>html heading inside pre</h2>
1210 1211 </pre>
1211 1212
1212 1213 h2. Subtitle after pre tag
1213 1214 RAW
1214 1215
1215 1216 @project = Project.find(1)
1216 1217 set_language_if_valid 'en'
1217 1218 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1218 1219
1219 1220 # heading that contains inline code
1220 1221 assert_match Regexp.new('<div class="contextual" title="Edit this section" id="section-4">' +
1221 1222 '<a href="/projects/1/wiki/Test/edit\?section=4"><img src="/images/edit.png(\?\d+)?" alt="Edit" /></a></div>' +
1222 1223 '<a name="Subtitle-with-inline-code"></a>' +
1223 1224 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1224 1225 result
1225 1226
1226 1227 # last heading
1227 1228 assert_match Regexp.new('<div class="contextual" title="Edit this section" id="section-5">' +
1228 1229 '<a href="/projects/1/wiki/Test/edit\?section=5"><img src="/images/edit.png(\?\d+)?" alt="Edit" /></a></div>' +
1229 1230 '<a name="Subtitle-after-pre-tag"></a>' +
1230 1231 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1231 1232 result
1232 1233 end
1233 1234
1234 1235 def test_default_formatter
1235 1236 with_settings :text_formatting => 'unknown' do
1236 1237 text = 'a *link*: http://www.example.net/'
1237 1238 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1238 1239 end
1239 1240 end
1240 1241
1241 1242 def test_due_date_distance_in_words
1242 1243 to_test = { Date.today => 'Due in 0 days',
1243 1244 Date.today + 1 => 'Due in 1 day',
1244 1245 Date.today + 100 => 'Due in about 3 months',
1245 1246 Date.today + 20000 => 'Due in over 54 years',
1246 1247 Date.today - 1 => '1 day late',
1247 1248 Date.today - 100 => 'about 3 months late',
1248 1249 Date.today - 20000 => 'over 54 years late',
1249 1250 }
1250 1251 ::I18n.locale = :en
1251 1252 to_test.each do |date, expected|
1252 1253 assert_equal expected, due_date_distance_in_words(date)
1253 1254 end
1254 1255 end
1255 1256
1256 1257 def test_avatar_enabled
1257 1258 with_settings :gravatar_enabled => '1' do
1258 1259 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1259 1260 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1260 1261 # Default size is 50
1261 1262 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1262 1263 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1263 1264 # Non-avatar options should be considered html options
1264 1265 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1265 1266 # The default class of the img tag should be gravatar
1266 1267 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1267 1268 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1268 1269 assert_nil avatar('jsmith')
1269 1270 assert_nil avatar(nil)
1270 1271 end
1271 1272 end
1272 1273
1273 1274 def test_avatar_disabled
1274 1275 with_settings :gravatar_enabled => '0' do
1275 1276 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1276 1277 end
1277 1278 end
1278 1279
1279 1280 def test_link_to_user
1280 1281 user = User.find(2)
1281 1282 result = link_to("John Smith", "/users/2", :class => "user active")
1282 1283 assert_equal result, link_to_user(user)
1283 1284 end
1284 1285
1285 1286 def test_link_to_user_should_not_link_to_locked_user
1286 1287 with_current_user nil do
1287 1288 user = User.find(5)
1288 1289 assert user.locked?
1289 1290 assert_equal 'Dave2 Lopper2', link_to_user(user)
1290 1291 end
1291 1292 end
1292 1293
1293 1294 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1294 1295 with_current_user User.find(1) do
1295 1296 user = User.find(5)
1296 1297 assert user.locked?
1297 1298 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1298 1299 assert_equal result, link_to_user(user)
1299 1300 end
1300 1301 end
1301 1302
1302 1303 def test_link_to_user_should_not_link_to_anonymous
1303 1304 user = User.anonymous
1304 1305 assert user.anonymous?
1305 1306 t = link_to_user(user)
1306 1307 assert_equal ::I18n.t(:label_user_anonymous), t
1307 1308 end
1308 1309
1309 1310 def test_link_to_attachment
1310 1311 a = Attachment.find(3)
1311 1312 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1312 1313 link_to_attachment(a)
1313 1314 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1314 1315 link_to_attachment(a, :text => 'Text')
1315 1316 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1316 1317 assert_equal result,
1317 1318 link_to_attachment(a, :class => 'foo')
1318 1319 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1319 1320 link_to_attachment(a, :download => true)
1320 1321 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1321 1322 link_to_attachment(a, :only_path => false)
1322 1323 end
1323 1324
1324 1325 def test_thumbnail_tag
1325 1326 a = Attachment.find(3)
1326 1327 assert_select_in thumbnail_tag(a),
1327 1328 'a[href=?][title=?] img[alt="3"][src=?]',
1328 1329 "/attachments/3/logo.gif", "logo.gif", "/attachments/thumbnail/3"
1329 1330 end
1330 1331
1331 1332 def test_link_to_project
1332 1333 project = Project.find(1)
1333 1334 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1334 1335 link_to_project(project)
1335 1336 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1336 1337 link_to_project(project, {:only_path => false, :jump => 'blah'})
1337 1338 end
1338 1339
1339 1340 def test_link_to_project_settings
1340 1341 project = Project.find(1)
1341 1342 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1342 1343
1343 1344 project.status = Project::STATUS_CLOSED
1344 1345 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1345 1346
1346 1347 project.status = Project::STATUS_ARCHIVED
1347 1348 assert_equal 'eCookbook', link_to_project_settings(project)
1348 1349 end
1349 1350
1350 1351 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1351 1352 # numeric identifier are no longer allowed
1352 1353 Project.where(:id => 1).update_all(:identifier => 25)
1353 1354 assert_equal '<a href="/projects/1">eCookbook</a>',
1354 1355 link_to_project(Project.find(1))
1355 1356 end
1356 1357
1357 1358 def test_principals_options_for_select_with_users
1358 1359 User.current = nil
1359 1360 users = [User.find(2), User.find(4)]
1360 1361 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1361 1362 principals_options_for_select(users)
1362 1363 end
1363 1364
1364 1365 def test_principals_options_for_select_with_selected
1365 1366 User.current = nil
1366 1367 users = [User.find(2), User.find(4)]
1367 1368 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1368 1369 principals_options_for_select(users, User.find(4))
1369 1370 end
1370 1371
1371 1372 def test_principals_options_for_select_with_users_and_groups
1372 1373 User.current = nil
1373 1374 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1374 1375 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1375 1376 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1376 1377 principals_options_for_select(users)
1377 1378 end
1378 1379
1379 1380 def test_principals_options_for_select_with_empty_collection
1380 1381 assert_equal '', principals_options_for_select([])
1381 1382 end
1382 1383
1383 1384 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1384 1385 users = [User.find(2), User.find(4)]
1385 1386 User.current = User.find(4)
1386 1387 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1387 1388 end
1388 1389
1389 1390 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1390 1391 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1391 1392 end
1392 1393
1393 1394 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1394 1395 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1395 1396 end
1396 1397
1397 1398 def test_image_tag_should_pick_the_default_image
1398 1399 assert_match 'src="/images/image.png"', image_tag("image.png")
1399 1400 end
1400 1401
1401 1402 def test_image_tag_should_pick_the_theme_image_if_it_exists
1402 1403 theme = Redmine::Themes.themes.last
1403 1404 theme.images << 'image.png'
1404 1405
1405 1406 with_settings :ui_theme => theme.id do
1406 1407 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1407 1408 assert_match %|src="/images/other.png"|, image_tag("other.png")
1408 1409 end
1409 1410 ensure
1410 1411 theme.images.delete 'image.png'
1411 1412 end
1412 1413
1413 1414 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1414 1415 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1415 1416 end
1416 1417
1417 1418 def test_javascript_include_tag_should_pick_the_default_javascript
1418 1419 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1419 1420 end
1420 1421
1421 1422 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1422 1423 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1423 1424 end
1424 1425
1425 1426 def test_raw_json_should_escape_closing_tags
1426 1427 s = raw_json(["<foo>bar</foo>"])
1427 1428 assert_include '\/foo', s
1428 1429 end
1429 1430
1430 1431 def test_raw_json_should_be_html_safe
1431 1432 s = raw_json(["foo"])
1432 1433 assert s.html_safe?
1433 1434 end
1434 1435
1435 1436 def test_html_title_should_app_title_if_not_set
1436 1437 assert_equal 'Redmine', html_title
1437 1438 end
1438 1439
1439 1440 def test_html_title_should_join_items
1440 1441 html_title 'Foo', 'Bar'
1441 1442 assert_equal 'Foo - Bar - Redmine', html_title
1442 1443 end
1443 1444
1444 1445 def test_html_title_should_append_current_project_name
1445 1446 @project = Project.find(1)
1446 1447 html_title 'Foo', 'Bar'
1447 1448 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1448 1449 end
1449 1450
1450 1451 def test_title_should_return_a_h2_tag
1451 1452 assert_equal '<h2>Foo</h2>', title('Foo')
1452 1453 end
1453 1454
1454 1455 def test_title_should_set_html_title
1455 1456 title('Foo')
1456 1457 assert_equal 'Foo - Redmine', html_title
1457 1458 end
1458 1459
1459 1460 def test_title_should_turn_arrays_into_links
1460 1461 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1461 1462 assert_equal 'Foo - Redmine', html_title
1462 1463 end
1463 1464
1464 1465 def test_title_should_join_items
1465 1466 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1466 1467 assert_equal 'Bar - Foo - Redmine', html_title
1467 1468 end
1468 1469
1469 1470 def test_favicon_path
1470 1471 assert_match %r{^/favicon\.ico}, favicon_path
1471 1472 end
1472 1473
1473 1474 def test_favicon_path_with_suburi
1474 1475 Redmine::Utils.relative_url_root = '/foo'
1475 1476 assert_match %r{^/foo/favicon\.ico}, favicon_path
1476 1477 ensure
1477 1478 Redmine::Utils.relative_url_root = ''
1478 1479 end
1479 1480
1480 1481 def test_favicon_url
1481 1482 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1482 1483 end
1483 1484
1484 1485 def test_favicon_url_with_suburi
1485 1486 Redmine::Utils.relative_url_root = '/foo'
1486 1487 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1487 1488 ensure
1488 1489 Redmine::Utils.relative_url_root = ''
1489 1490 end
1490 1491
1491 1492 def test_truncate_single_line
1492 1493 str = "01234"
1493 1494 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1494 1495 assert_equal "01234 0...", result
1495 1496 assert !result.html_safe?
1496 1497 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1497 1498 assert_equal "01234<&#> 012...", result
1498 1499 assert !result.html_safe?
1499 1500 end
1500 1501
1501 1502 def test_truncate_single_line_non_ascii
1502 1503 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e".force_encoding('UTF-8')
1503 1504 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1504 1505 assert_equal "#{ja} #{ja}...", result
1505 1506 assert !result.html_safe?
1506 1507 end
1507 1508 end
@@ -1,970 +1,971
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class MailHandlerTest < ActiveSupport::TestCase
23 23 fixtures :users, :projects, :enabled_modules, :roles,
24 24 :members, :member_roles, :users,
25 :email_addresses,
25 26 :issues, :issue_statuses,
26 27 :workflows, :trackers, :projects_trackers,
27 28 :versions, :enumerations, :issue_categories,
28 29 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 30 :boards, :messages
30 31
31 32 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32 33
33 34 def setup
34 35 ActionMailer::Base.deliveries.clear
35 36 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 37 end
37 38
38 39 def teardown
39 40 Setting.clear_cache
40 41 end
41 42
42 43 def test_add_issue
43 44 ActionMailer::Base.deliveries.clear
44 45 lft1 = new_issue_lft
45 46 # This email contains: 'Project: onlinestore'
46 47 issue = submit_email('ticket_on_given_project.eml')
47 48 assert issue.is_a?(Issue)
48 49 assert !issue.new_record?
49 50 issue.reload
50 51 assert_equal Project.find(2), issue.project
51 52 assert_equal issue.project.trackers.first, issue.tracker
52 53 assert_equal 'New ticket on a given project', issue.subject
53 54 assert_equal User.find_by_login('jsmith'), issue.author
54 55 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
55 56 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
56 57 assert_equal '2010-01-01', issue.start_date.to_s
57 58 assert_equal '2010-12-31', issue.due_date.to_s
58 59 assert_equal User.find_by_login('jsmith'), issue.assigned_to
59 60 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
60 61 assert_equal 2.5, issue.estimated_hours
61 62 assert_equal 30, issue.done_ratio
62 63 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
63 64 # keywords should be removed from the email body
64 65 assert !issue.description.match(/^Project:/i)
65 66 assert !issue.description.match(/^Status:/i)
66 67 assert !issue.description.match(/^Start Date:/i)
67 68 # Email notification should be sent
68 69 mail = ActionMailer::Base.deliveries.last
69 70 assert_not_nil mail
70 71 assert mail.subject.include?("##{issue.id}")
71 72 assert mail.subject.include?('New ticket on a given project')
72 73 end
73 74
74 75 def test_add_issue_with_default_tracker
75 76 # This email contains: 'Project: onlinestore'
76 77 issue = submit_email(
77 78 'ticket_on_given_project.eml',
78 79 :issue => {:tracker => 'Support request'}
79 80 )
80 81 assert issue.is_a?(Issue)
81 82 assert !issue.new_record?
82 83 issue.reload
83 84 assert_equal 'Support request', issue.tracker.name
84 85 end
85 86
86 87 def test_add_issue_with_status
87 88 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
88 89 issue = submit_email('ticket_on_given_project.eml')
89 90 assert issue.is_a?(Issue)
90 91 assert !issue.new_record?
91 92 issue.reload
92 93 assert_equal Project.find(2), issue.project
93 94 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
94 95 end
95 96
96 97 def test_add_issue_with_attributes_override
97 98 issue = submit_email(
98 99 'ticket_with_attributes.eml',
99 100 :allow_override => 'tracker,category,priority'
100 101 )
101 102 assert issue.is_a?(Issue)
102 103 assert !issue.new_record?
103 104 issue.reload
104 105 assert_equal 'New ticket on a given project', issue.subject
105 106 assert_equal User.find_by_login('jsmith'), issue.author
106 107 assert_equal Project.find(2), issue.project
107 108 assert_equal 'Feature request', issue.tracker.to_s
108 109 assert_equal 'Stock management', issue.category.to_s
109 110 assert_equal 'Urgent', issue.priority.to_s
110 111 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
111 112 end
112 113
113 114 def test_add_issue_with_group_assignment
114 115 with_settings :issue_group_assignment => '1' do
115 116 issue = submit_email('ticket_on_given_project.eml') do |email|
116 117 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
117 118 end
118 119 assert issue.is_a?(Issue)
119 120 assert !issue.new_record?
120 121 issue.reload
121 122 assert_equal Group.find(11), issue.assigned_to
122 123 end
123 124 end
124 125
125 126 def test_add_issue_with_partial_attributes_override
126 127 issue = submit_email(
127 128 'ticket_with_attributes.eml',
128 129 :issue => {:priority => 'High'},
129 130 :allow_override => ['tracker']
130 131 )
131 132 assert issue.is_a?(Issue)
132 133 assert !issue.new_record?
133 134 issue.reload
134 135 assert_equal 'New ticket on a given project', issue.subject
135 136 assert_equal User.find_by_login('jsmith'), issue.author
136 137 assert_equal Project.find(2), issue.project
137 138 assert_equal 'Feature request', issue.tracker.to_s
138 139 assert_nil issue.category
139 140 assert_equal 'High', issue.priority.to_s
140 141 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
141 142 end
142 143
143 144 def test_add_issue_with_spaces_between_attribute_and_separator
144 145 issue = submit_email(
145 146 'ticket_with_spaces_between_attribute_and_separator.eml',
146 147 :allow_override => 'tracker,category,priority'
147 148 )
148 149 assert issue.is_a?(Issue)
149 150 assert !issue.new_record?
150 151 issue.reload
151 152 assert_equal 'New ticket on a given project', issue.subject
152 153 assert_equal User.find_by_login('jsmith'), issue.author
153 154 assert_equal Project.find(2), issue.project
154 155 assert_equal 'Feature request', issue.tracker.to_s
155 156 assert_equal 'Stock management', issue.category.to_s
156 157 assert_equal 'Urgent', issue.priority.to_s
157 158 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
158 159 end
159 160
160 161 def test_add_issue_with_attachment_to_specific_project
161 162 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
162 163 assert issue.is_a?(Issue)
163 164 assert !issue.new_record?
164 165 issue.reload
165 166 assert_equal 'Ticket created by email with attachment', issue.subject
166 167 assert_equal User.find_by_login('jsmith'), issue.author
167 168 assert_equal Project.find(2), issue.project
168 169 assert_equal 'This is a new ticket with attachments', issue.description
169 170 # Attachment properties
170 171 assert_equal 1, issue.attachments.size
171 172 assert_equal 'Paella.jpg', issue.attachments.first.filename
172 173 assert_equal 'image/jpeg', issue.attachments.first.content_type
173 174 assert_equal 10790, issue.attachments.first.filesize
174 175 end
175 176
176 177 def test_add_issue_with_custom_fields
177 178 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
178 179 assert issue.is_a?(Issue)
179 180 assert !issue.new_record?
180 181 issue.reload
181 182 assert_equal 'New ticket with custom field values', issue.subject
182 183 assert_equal 'PostgreSQL', issue.custom_field_value(1)
183 184 assert_equal 'Value for a custom field', issue.custom_field_value(2)
184 185 assert !issue.description.match(/^searchable field:/i)
185 186 end
186 187
187 188 def test_add_issue_with_version_custom_fields
188 189 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
189 190
190 191 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
191 192 email << "Affected version: 1.0\n"
192 193 end
193 194 assert issue.is_a?(Issue)
194 195 assert !issue.new_record?
195 196 issue.reload
196 197 assert_equal '2', issue.custom_field_value(field)
197 198 end
198 199
199 200 def test_add_issue_should_match_assignee_on_display_name
200 201 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
201 202 User.add_to_project(user, Project.find(2))
202 203 issue = submit_email('ticket_on_given_project.eml') do |email|
203 204 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
204 205 end
205 206 assert issue.is_a?(Issue)
206 207 assert_equal user, issue.assigned_to
207 208 end
208 209
209 210 def test_add_issue_should_set_default_start_date
210 211 with_settings :default_issue_start_date_to_creation_date => '1' do
211 212 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
212 213 assert issue.is_a?(Issue)
213 214 assert_equal Date.today, issue.start_date
214 215 end
215 216 end
216 217
217 218 def test_add_issue_with_cc
218 219 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
219 220 assert issue.is_a?(Issue)
220 221 assert !issue.new_record?
221 222 issue.reload
222 223 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
223 224 assert_equal 1, issue.watcher_user_ids.size
224 225 end
225 226
226 227 def test_add_issue_from_additional_email_address
227 228 user = User.find(2)
228 229 user.mail = 'mainaddress@somenet.foo'
229 230 user.save!
230 231 EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
231 232
232 233 issue = submit_email('ticket_on_given_project.eml')
233 234 assert issue
234 235 assert_equal user, issue.author
235 236 end
236 237
237 238 def test_add_issue_by_unknown_user
238 239 assert_no_difference 'User.count' do
239 240 assert_equal false,
240 241 submit_email(
241 242 'ticket_by_unknown_user.eml',
242 243 :issue => {:project => 'ecookbook'}
243 244 )
244 245 end
245 246 end
246 247
247 248 def test_add_issue_by_anonymous_user
248 249 Role.anonymous.add_permission!(:add_issues)
249 250 assert_no_difference 'User.count' do
250 251 issue = submit_email(
251 252 'ticket_by_unknown_user.eml',
252 253 :issue => {:project => 'ecookbook'},
253 254 :unknown_user => 'accept'
254 255 )
255 256 assert issue.is_a?(Issue)
256 257 assert issue.author.anonymous?
257 258 end
258 259 end
259 260
260 261 def test_add_issue_by_anonymous_user_with_no_from_address
261 262 Role.anonymous.add_permission!(:add_issues)
262 263 assert_no_difference 'User.count' do
263 264 issue = submit_email(
264 265 'ticket_by_empty_user.eml',
265 266 :issue => {:project => 'ecookbook'},
266 267 :unknown_user => 'accept'
267 268 )
268 269 assert issue.is_a?(Issue)
269 270 assert issue.author.anonymous?
270 271 end
271 272 end
272 273
273 274 def test_add_issue_by_anonymous_user_on_private_project
274 275 Role.anonymous.add_permission!(:add_issues)
275 276 assert_no_difference 'User.count' do
276 277 assert_no_difference 'Issue.count' do
277 278 assert_equal false,
278 279 submit_email(
279 280 'ticket_by_unknown_user.eml',
280 281 :issue => {:project => 'onlinestore'},
281 282 :unknown_user => 'accept'
282 283 )
283 284 end
284 285 end
285 286 end
286 287
287 288 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
288 289 lft1 = new_issue_lft
289 290 assert_no_difference 'User.count' do
290 291 assert_difference 'Issue.count' do
291 292 issue = submit_email(
292 293 'ticket_by_unknown_user.eml',
293 294 :issue => {:project => 'onlinestore'},
294 295 :no_permission_check => '1',
295 296 :unknown_user => 'accept'
296 297 )
297 298 assert issue.is_a?(Issue)
298 299 assert issue.author.anonymous?
299 300 assert !issue.project.is_public?
300 301 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
301 302 end
302 303 end
303 304 end
304 305
305 306 def test_add_issue_by_created_user
306 307 Setting.default_language = 'en'
307 308 assert_difference 'User.count' do
308 309 issue = submit_email(
309 310 'ticket_by_unknown_user.eml',
310 311 :issue => {:project => 'ecookbook'},
311 312 :unknown_user => 'create'
312 313 )
313 314 assert issue.is_a?(Issue)
314 315 assert issue.author.active?
315 316 assert_equal 'john.doe@somenet.foo', issue.author.mail
316 317 assert_equal 'John', issue.author.firstname
317 318 assert_equal 'Doe', issue.author.lastname
318 319
319 320 # account information
320 321 email = ActionMailer::Base.deliveries.first
321 322 assert_not_nil email
322 323 assert email.subject.include?('account activation')
323 324 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
324 325 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
325 326 assert_equal issue.author, User.try_to_login(login, password)
326 327 end
327 328 end
328 329
329 330 def test_created_user_should_be_added_to_groups
330 331 group1 = Group.generate!
331 332 group2 = Group.generate!
332 333
333 334 assert_difference 'User.count' do
334 335 submit_email(
335 336 'ticket_by_unknown_user.eml',
336 337 :issue => {:project => 'ecookbook'},
337 338 :unknown_user => 'create',
338 339 :default_group => "#{group1.name},#{group2.name}"
339 340 )
340 341 end
341 342 user = User.order('id DESC').first
342 343 assert_equal [group1, group2].sort, user.groups.sort
343 344 end
344 345
345 346 def test_created_user_should_not_receive_account_information_with_no_account_info_option
346 347 assert_difference 'User.count' do
347 348 submit_email(
348 349 'ticket_by_unknown_user.eml',
349 350 :issue => {:project => 'ecookbook'},
350 351 :unknown_user => 'create',
351 352 :no_account_notice => '1'
352 353 )
353 354 end
354 355
355 356 # only 1 email for the new issue notification
356 357 assert_equal 1, ActionMailer::Base.deliveries.size
357 358 email = ActionMailer::Base.deliveries.first
358 359 assert_include 'Ticket by unknown user', email.subject
359 360 end
360 361
361 362 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
362 363 assert_difference 'User.count' do
363 364 submit_email(
364 365 'ticket_by_unknown_user.eml',
365 366 :issue => {:project => 'ecookbook'},
366 367 :unknown_user => 'create',
367 368 :no_notification => '1'
368 369 )
369 370 end
370 371 user = User.order('id DESC').first
371 372 assert_equal 'none', user.mail_notification
372 373 end
373 374
374 375 def test_add_issue_without_from_header
375 376 Role.anonymous.add_permission!(:add_issues)
376 377 assert_equal false, submit_email('ticket_without_from_header.eml')
377 378 end
378 379
379 380 def test_add_issue_with_invalid_attributes
380 381 with_settings :default_issue_start_date_to_creation_date => '0' do
381 382 issue = submit_email(
382 383 'ticket_with_invalid_attributes.eml',
383 384 :allow_override => 'tracker,category,priority'
384 385 )
385 386 assert issue.is_a?(Issue)
386 387 assert !issue.new_record?
387 388 issue.reload
388 389 assert_nil issue.assigned_to
389 390 assert_nil issue.start_date
390 391 assert_nil issue.due_date
391 392 assert_equal 0, issue.done_ratio
392 393 assert_equal 'Normal', issue.priority.to_s
393 394 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
394 395 end
395 396 end
396 397
397 398 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
398 399 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
399 400 email.gsub!(/^Project:.+$/, 'Project: invalid')
400 401 end
401 402 assert issue.is_a?(Issue)
402 403 assert !issue.new_record?
403 404 assert_equal 'ecookbook', issue.project.identifier
404 405 end
405 406
406 407 def test_add_issue_with_localized_attributes
407 408 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
408 409 issue = submit_email(
409 410 'ticket_with_localized_attributes.eml',
410 411 :allow_override => 'tracker,category,priority'
411 412 )
412 413 assert issue.is_a?(Issue)
413 414 assert !issue.new_record?
414 415 issue.reload
415 416 assert_equal 'New ticket on a given project', issue.subject
416 417 assert_equal User.find_by_login('jsmith'), issue.author
417 418 assert_equal Project.find(2), issue.project
418 419 assert_equal 'Feature request', issue.tracker.to_s
419 420 assert_equal 'Stock management', issue.category.to_s
420 421 assert_equal 'Urgent', issue.priority.to_s
421 422 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
422 423 end
423 424
424 425 def test_add_issue_with_japanese_keywords
425 426 ja_dev = "\xe9\x96\x8b\xe7\x99\xba".force_encoding('UTF-8')
426 427 tracker = Tracker.generate!(:name => ja_dev)
427 428 Project.find(1).trackers << tracker
428 429 issue = submit_email(
429 430 'japanese_keywords_iso_2022_jp.eml',
430 431 :issue => {:project => 'ecookbook'},
431 432 :allow_override => 'tracker'
432 433 )
433 434 assert_kind_of Issue, issue
434 435 assert_equal tracker, issue.tracker
435 436 end
436 437
437 438 def test_add_issue_from_apple_mail
438 439 issue = submit_email(
439 440 'apple_mail_with_attachment.eml',
440 441 :issue => {:project => 'ecookbook'}
441 442 )
442 443 assert_kind_of Issue, issue
443 444 assert_equal 1, issue.attachments.size
444 445
445 446 attachment = issue.attachments.first
446 447 assert_equal 'paella.jpg', attachment.filename
447 448 assert_equal 10790, attachment.filesize
448 449 assert File.exist?(attachment.diskfile)
449 450 assert_equal 10790, File.size(attachment.diskfile)
450 451 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
451 452 end
452 453
453 454 def test_thunderbird_with_attachment_ja
454 455 issue = submit_email(
455 456 'thunderbird_with_attachment_ja.eml',
456 457 :issue => {:project => 'ecookbook'}
457 458 )
458 459 assert_kind_of Issue, issue
459 460 assert_equal 1, issue.attachments.size
460 461 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
461 462 attachment = issue.attachments.first
462 463 assert_equal ja, attachment.filename
463 464 assert_equal 5, attachment.filesize
464 465 assert File.exist?(attachment.diskfile)
465 466 assert_equal 5, File.size(attachment.diskfile)
466 467 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
467 468 end
468 469
469 470 def test_gmail_with_attachment_ja
470 471 issue = submit_email(
471 472 'gmail_with_attachment_ja.eml',
472 473 :issue => {:project => 'ecookbook'}
473 474 )
474 475 assert_kind_of Issue, issue
475 476 assert_equal 1, issue.attachments.size
476 477 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
477 478 attachment = issue.attachments.first
478 479 assert_equal ja, attachment.filename
479 480 assert_equal 5, attachment.filesize
480 481 assert File.exist?(attachment.diskfile)
481 482 assert_equal 5, File.size(attachment.diskfile)
482 483 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
483 484 end
484 485
485 486 def test_thunderbird_with_attachment_latin1
486 487 issue = submit_email(
487 488 'thunderbird_with_attachment_iso-8859-1.eml',
488 489 :issue => {:project => 'ecookbook'}
489 490 )
490 491 assert_kind_of Issue, issue
491 492 assert_equal 1, issue.attachments.size
492 493 u = "".force_encoding('UTF-8')
493 494 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
494 495 11.times { u << u1 }
495 496 attachment = issue.attachments.first
496 497 assert_equal "#{u}.png", attachment.filename
497 498 assert_equal 130, attachment.filesize
498 499 assert File.exist?(attachment.diskfile)
499 500 assert_equal 130, File.size(attachment.diskfile)
500 501 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
501 502 end
502 503
503 504 def test_gmail_with_attachment_latin1
504 505 issue = submit_email(
505 506 'gmail_with_attachment_iso-8859-1.eml',
506 507 :issue => {:project => 'ecookbook'}
507 508 )
508 509 assert_kind_of Issue, issue
509 510 assert_equal 1, issue.attachments.size
510 511 u = "".force_encoding('UTF-8')
511 512 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
512 513 11.times { u << u1 }
513 514 attachment = issue.attachments.first
514 515 assert_equal "#{u}.txt", attachment.filename
515 516 assert_equal 5, attachment.filesize
516 517 assert File.exist?(attachment.diskfile)
517 518 assert_equal 5, File.size(attachment.diskfile)
518 519 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
519 520 end
520 521
521 522 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
522 523 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
523 524 assert_include 'first', issue.description
524 525 assert_include 'second', issue.description
525 526 assert_include 'third', issue.description
526 527 end
527 528
528 529 def test_attachment_text_part_should_be_added_as_issue_attachment
529 530 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
530 531 assert_not_include 'Plain text attachment', issue.description
531 532 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
532 533 assert_not_nil attachment
533 534 assert_include 'Plain text attachment', File.read(attachment.diskfile)
534 535 end
535 536
536 537 def test_add_issue_with_iso_8859_1_subject
537 538 issue = submit_email(
538 539 'subject_as_iso-8859-1.eml',
539 540 :issue => {:project => 'ecookbook'}
540 541 )
541 542 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc...".force_encoding('UTF-8')
542 543 assert_kind_of Issue, issue
543 544 assert_equal str, issue.subject
544 545 end
545 546
546 547 def test_quoted_printable_utf8
547 548 issue = submit_email(
548 549 'quoted_printable_utf8.eml',
549 550 :issue => {:project => 'ecookbook'}
550 551 )
551 552 assert_kind_of Issue, issue
552 553 str = "Freundliche Gr\xc3\xbcsse".force_encoding('UTF-8')
553 554 assert_equal str, issue.description
554 555 end
555 556
556 557 def test_gmail_iso8859_2
557 558 issue = submit_email(
558 559 'gmail-iso8859-2.eml',
559 560 :issue => {:project => 'ecookbook'}
560 561 )
561 562 assert_kind_of Issue, issue
562 563 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87.".force_encoding('UTF-8')
563 564 assert issue.description.include?(str)
564 565 end
565 566
566 567 def test_add_issue_with_japanese_subject
567 568 issue = submit_email(
568 569 'subject_japanese_1.eml',
569 570 :issue => {:project => 'ecookbook'}
570 571 )
571 572 assert_kind_of Issue, issue
572 573 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
573 574 assert_equal ja, issue.subject
574 575 end
575 576
576 577 def test_add_issue_with_korean_body
577 578 # Make sure mail bodies with a charset unknown to Ruby
578 579 # but known to the Mail gem 2.5.4 are handled correctly
579 580 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4.".force_encoding('UTF-8')
580 581 issue = submit_email(
581 582 'body_ks_c_5601-1987.eml',
582 583 :issue => {:project => 'ecookbook'}
583 584 )
584 585 assert_kind_of Issue, issue
585 586 assert_equal kr, issue.description
586 587 end
587 588
588 589 def test_add_issue_with_no_subject_header
589 590 issue = submit_email(
590 591 'no_subject_header.eml',
591 592 :issue => {:project => 'ecookbook'}
592 593 )
593 594 assert_kind_of Issue, issue
594 595 assert_equal '(no subject)', issue.subject
595 596 end
596 597
597 598 def test_add_issue_with_mixed_japanese_subject
598 599 issue = submit_email(
599 600 'subject_japanese_2.eml',
600 601 :issue => {:project => 'ecookbook'}
601 602 )
602 603 assert_kind_of Issue, issue
603 604 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
604 605 assert_equal ja, issue.subject
605 606 end
606 607
607 608 def test_should_ignore_emails_from_locked_users
608 609 User.find(2).lock!
609 610
610 611 MailHandler.any_instance.expects(:dispatch).never
611 612 assert_no_difference 'Issue.count' do
612 613 assert_equal false, submit_email('ticket_on_given_project.eml')
613 614 end
614 615 end
615 616
616 617 def test_should_ignore_emails_from_emission_address
617 618 Role.anonymous.add_permission!(:add_issues)
618 619 assert_no_difference 'User.count' do
619 620 assert_equal false,
620 621 submit_email(
621 622 'ticket_from_emission_address.eml',
622 623 :issue => {:project => 'ecookbook'},
623 624 :unknown_user => 'create'
624 625 )
625 626 end
626 627 end
627 628
628 629 def test_should_ignore_auto_replied_emails
629 630 MailHandler.any_instance.expects(:dispatch).never
630 631 [
631 632 "X-Auto-Response-Suppress: OOF",
632 633 "Auto-Submitted: auto-replied",
633 634 "Auto-Submitted: Auto-Replied",
634 635 "Auto-Submitted: auto-generated",
635 636 'X-Autoreply: yes'
636 637 ].each do |header|
637 638 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
638 639 raw = header + "\n" + raw
639 640
640 641 assert_no_difference 'Issue.count' do
641 642 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
642 643 end
643 644 end
644 645 end
645 646
646 647 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
647 648 [
648 649 "Auto-Submitted: auto-forwarded"
649 650 ].each do |header|
650 651 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
651 652 raw = header + "\n" + raw
652 653
653 654 assert_difference 'Issue.count', 1 do
654 655 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
655 656 end
656 657 end
657 658 end
658 659
659 660 def test_add_issue_should_send_email_notification
660 661 Setting.notified_events = ['issue_added']
661 662 ActionMailer::Base.deliveries.clear
662 663 # This email contains: 'Project: onlinestore'
663 664 issue = submit_email('ticket_on_given_project.eml')
664 665 assert issue.is_a?(Issue)
665 666 assert_equal 1, ActionMailer::Base.deliveries.size
666 667 end
667 668
668 669 def test_update_issue
669 670 journal = submit_email('ticket_reply.eml')
670 671 assert journal.is_a?(Journal)
671 672 assert_equal User.find_by_login('jsmith'), journal.user
672 673 assert_equal Issue.find(2), journal.journalized
673 674 assert_match /This is reply/, journal.notes
674 675 assert_equal false, journal.private_notes
675 676 assert_equal 'Feature request', journal.issue.tracker.name
676 677 end
677 678
678 679 def test_update_issue_should_accept_issue_id_after_space_inside_brackets
679 680 journal = submit_email('ticket_reply_with_status.eml') do |email|
680 681 assert email.sub!(/^Subject:.*$/, "Subject: Re: [Feature request #2] Add ingredients categories")
681 682 end
682 683 assert journal.is_a?(Journal)
683 684 assert_equal Issue.find(2), journal.journalized
684 685 end
685 686
686 687 def test_update_issue_should_accept_issue_id_inside_brackets
687 688 journal = submit_email('ticket_reply_with_status.eml') do |email|
688 689 assert email.sub!(/^Subject:.*$/, "Subject: Re: [#2] Add ingredients categories")
689 690 end
690 691 assert journal.is_a?(Journal)
691 692 assert_equal Issue.find(2), journal.journalized
692 693 end
693 694
694 695 def test_update_issue_should_ignore_bogus_issue_ids_in_subject
695 696 journal = submit_email('ticket_reply_with_status.eml') do |email|
696 697 assert email.sub!(/^Subject:.*$/, "Subject: Re: [12345#1][bogus#1][Feature request #2] Add ingredients categories")
697 698 end
698 699 assert journal.is_a?(Journal)
699 700 assert_equal Issue.find(2), journal.journalized
700 701 end
701 702
702 703 def test_update_issue_with_attribute_changes
703 704 # This email contains: 'Status: Resolved'
704 705 journal = submit_email('ticket_reply_with_status.eml')
705 706 assert journal.is_a?(Journal)
706 707 issue = Issue.find(journal.issue.id)
707 708 assert_equal User.find_by_login('jsmith'), journal.user
708 709 assert_equal Issue.find(2), journal.journalized
709 710 assert_match /This is reply/, journal.notes
710 711 assert_equal 'Feature request', journal.issue.tracker.name
711 712 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
712 713 assert_equal '2010-01-01', issue.start_date.to_s
713 714 assert_equal '2010-12-31', issue.due_date.to_s
714 715 assert_equal User.find_by_login('jsmith'), issue.assigned_to
715 716 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
716 717 # keywords should be removed from the email body
717 718 assert !journal.notes.match(/^Status:/i)
718 719 assert !journal.notes.match(/^Start Date:/i)
719 720 end
720 721
721 722 def test_update_issue_with_attachment
722 723 assert_difference 'Journal.count' do
723 724 assert_difference 'JournalDetail.count' do
724 725 assert_difference 'Attachment.count' do
725 726 assert_no_difference 'Issue.count' do
726 727 journal = submit_email('ticket_with_attachment.eml') do |raw|
727 728 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
728 729 end
729 730 end
730 731 end
731 732 end
732 733 end
733 734 journal = Journal.order('id DESC').first
734 735 assert_equal Issue.find(2), journal.journalized
735 736 assert_equal 1, journal.details.size
736 737
737 738 detail = journal.details.first
738 739 assert_equal 'attachment', detail.property
739 740 assert_equal 'Paella.jpg', detail.value
740 741 end
741 742
742 743 def test_update_issue_should_send_email_notification
743 744 ActionMailer::Base.deliveries.clear
744 745 journal = submit_email('ticket_reply.eml')
745 746 assert journal.is_a?(Journal)
746 747 assert_equal 1, ActionMailer::Base.deliveries.size
747 748 end
748 749
749 750 def test_update_issue_should_not_set_defaults
750 751 journal = submit_email(
751 752 'ticket_reply.eml',
752 753 :issue => {:tracker => 'Support request', :priority => 'High'}
753 754 )
754 755 assert journal.is_a?(Journal)
755 756 assert_match /This is reply/, journal.notes
756 757 assert_equal 'Feature request', journal.issue.tracker.name
757 758 assert_equal 'Normal', journal.issue.priority.name
758 759 end
759 760
760 761 def test_replying_to_a_private_note_should_add_reply_as_private
761 762 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
762 763
763 764 assert_difference 'Journal.count' do
764 765 journal = submit_email('ticket_reply.eml') do |email|
765 766 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
766 767 end
767 768
768 769 assert_kind_of Journal, journal
769 770 assert_match /This is reply/, journal.notes
770 771 assert_equal true, journal.private_notes
771 772 end
772 773 end
773 774
774 775 def test_reply_to_a_message
775 776 m = submit_email('message_reply.eml')
776 777 assert m.is_a?(Message)
777 778 assert !m.new_record?
778 779 m.reload
779 780 assert_equal 'Reply via email', m.subject
780 781 # The email replies to message #2 which is part of the thread of message #1
781 782 assert_equal Message.find(1), m.parent
782 783 end
783 784
784 785 def test_reply_to_a_message_by_subject
785 786 m = submit_email('message_reply_by_subject.eml')
786 787 assert m.is_a?(Message)
787 788 assert !m.new_record?
788 789 m.reload
789 790 assert_equal 'Reply to the first post', m.subject
790 791 assert_equal Message.find(1), m.parent
791 792 end
792 793
793 794 def test_should_strip_tags_of_html_only_emails
794 795 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
795 796 assert issue.is_a?(Issue)
796 797 assert !issue.new_record?
797 798 issue.reload
798 799 assert_equal 'HTML email', issue.subject
799 800 assert_equal 'This is a html-only email.', issue.description
800 801 end
801 802
802 803 test "truncate emails with no setting should add the entire email into the issue" do
803 804 with_settings :mail_handler_body_delimiters => '' do
804 805 issue = submit_email('ticket_on_given_project.eml')
805 806 assert_issue_created(issue)
806 807 assert issue.description.include?('---')
807 808 assert issue.description.include?('This paragraph is after the delimiter')
808 809 end
809 810 end
810 811
811 812 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
812 813 with_settings :mail_handler_body_delimiters => '---' do
813 814 issue = submit_email('ticket_on_given_project.eml')
814 815 assert_issue_created(issue)
815 816 assert issue.description.include?('This paragraph is before delimiters')
816 817 assert issue.description.include?('--- This line starts with a delimiter')
817 818 assert !issue.description.match(/^---$/)
818 819 assert !issue.description.include?('This paragraph is after the delimiter')
819 820 end
820 821 end
821 822
822 823 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
823 824 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
824 825 journal = submit_email('issue_update_with_quoted_reply_above.eml')
825 826 assert journal.is_a?(Journal)
826 827 assert journal.notes.include?('An update to the issue by the sender.')
827 828 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
828 829 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
829 830 end
830 831 end
831 832
832 833 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
833 834 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
834 835 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
835 836 assert journal.is_a?(Journal)
836 837 assert journal.notes.include?('An update to the issue by the sender.')
837 838 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
838 839 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
839 840 end
840 841 end
841 842
842 843 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
843 844 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
844 845 issue = submit_email('ticket_on_given_project.eml')
845 846 assert_issue_created(issue)
846 847 assert issue.description.include?('This paragraph is before delimiters')
847 848 assert !issue.description.include?('BREAK')
848 849 assert !issue.description.include?('This paragraph is between delimiters')
849 850 assert !issue.description.match(/^---$/)
850 851 assert !issue.description.include?('This paragraph is after the delimiter')
851 852 end
852 853 end
853 854
854 855 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
855 856 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
856 857 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
857 858 assert issue.is_a?(Issue)
858 859 assert !issue.new_record?
859 860 assert_equal 0, issue.reload.attachments.size
860 861 end
861 862 end
862 863
863 864 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
864 865 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
865 866 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
866 867 assert issue.is_a?(Issue)
867 868 assert !issue.new_record?
868 869 assert_equal 1, issue.reload.attachments.size
869 870 end
870 871 end
871 872
872 873 def test_email_with_long_subject_line
873 874 issue = submit_email('ticket_with_long_subject.eml')
874 875 assert issue.is_a?(Issue)
875 876 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
876 877 end
877 878
878 879 def test_first_keyword_should_be_matched
879 880 issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
880 881 assert issue.is_a?(Issue)
881 882 assert_equal 'High', issue.priority.name
882 883 end
883 884
884 885 def test_keyword_after_delimiter_should_be_ignored
885 886 with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
886 887 issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
887 888 assert issue.is_a?(Issue)
888 889 assert_equal 'Normal', issue.priority.name
889 890 end
890 891 end
891 892
892 893 def test_new_user_from_attributes_should_return_valid_user
893 894 to_test = {
894 895 # [address, name] => [login, firstname, lastname]
895 896 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
896 897 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
897 898 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
898 899 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
899 900 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
900 901 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
901 902 }
902 903
903 904 to_test.each do |attrs, expected|
904 905 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
905 906
906 907 assert user.valid?, user.errors.full_messages.to_s
907 908 assert_equal attrs.first, user.mail
908 909 assert_equal expected[0], user.login
909 910 assert_equal expected[1], user.firstname
910 911 assert_equal expected[2], user.lastname
911 912 assert_equal 'only_my_events', user.mail_notification
912 913 end
913 914 end
914 915
915 916 def test_new_user_from_attributes_should_use_default_login_if_invalid
916 917 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
917 918 assert user.valid?
918 919 assert user.login =~ /^user[a-f0-9]+$/
919 920 assert_equal 'foo+bar@example.net', user.mail
920 921 end
921 922
922 923 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
923 924 assert_difference 'User.count' do
924 925 issue = submit_email(
925 926 'fullname_of_sender_as_utf8_encoded.eml',
926 927 :issue => {:project => 'ecookbook'},
927 928 :unknown_user => 'create'
928 929 )
929 930 end
930 931 user = User.order('id DESC').first
931 932 assert_equal "foo@example.org", user.mail
932 933 str1 = "\xc3\x84\xc3\xa4".force_encoding('UTF-8')
933 934 str2 = "\xc3\x96\xc3\xb6".force_encoding('UTF-8')
934 935 assert_equal str1, user.firstname
935 936 assert_equal str2, user.lastname
936 937 end
937 938
938 939 def test_extract_options_from_env_should_return_options
939 940 options = MailHandler.extract_options_from_env({
940 941 'tracker' => 'defect',
941 942 'project' => 'foo',
942 943 'unknown_user' => 'create'
943 944 })
944 945
945 946 assert_equal({
946 947 :issue => {:tracker => 'defect', :project => 'foo'},
947 948 :unknown_user => 'create'
948 949 }, options)
949 950 end
950 951
951 952 def test_safe_receive_should_rescue_exceptions_and_return_false
952 953 MailHandler.stubs(:receive).raises(Exception.new "Something went wrong")
953 954
954 955 assert_equal false, MailHandler.safe_receive
955 956 end
956 957
957 958 private
958 959
959 960 def submit_email(filename, options={})
960 961 raw = IO.read(File.join(FIXTURES_PATH, filename))
961 962 yield raw if block_given?
962 963 MailHandler.receive(raw, options)
963 964 end
964 965
965 966 def assert_issue_created(issue)
966 967 assert issue.is_a?(Issue)
967 968 assert !issue.new_record?
968 969 issue.reload
969 970 end
970 971 end
@@ -1,468 +1,469
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoryTest < ActiveSupport::TestCase
21 21 fixtures :projects,
22 22 :trackers,
23 23 :projects_trackers,
24 24 :enabled_modules,
25 25 :repositories,
26 26 :issues,
27 27 :issue_statuses,
28 28 :issue_categories,
29 29 :changesets,
30 30 :changes,
31 31 :users,
32 :email_addresses,
32 33 :members,
33 34 :member_roles,
34 35 :roles,
35 36 :enumerations
36 37
37 38 include Redmine::I18n
38 39
39 40 def setup
40 41 @repository = Project.find(1).repository
41 42 end
42 43
43 44 def test_blank_log_encoding_error_message
44 45 set_language_if_valid 'en'
45 46 repo = Repository::Bazaar.new(
46 47 :project => Project.find(3),
47 48 :url => "/test",
48 49 :log_encoding => ''
49 50 )
50 51 assert !repo.save
51 52 assert_include "Commit messages encoding cannot be blank",
52 53 repo.errors.full_messages
53 54 end
54 55
55 56 def test_blank_log_encoding_error_message_fr
56 57 set_language_if_valid 'fr'
57 58 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
58 59 repo = Repository::Bazaar.new(
59 60 :project => Project.find(3),
60 61 :url => "/test"
61 62 )
62 63 assert !repo.save
63 64 assert_include str, repo.errors.full_messages
64 65 end
65 66
66 67 def test_create
67 68 repository = Repository::Subversion.new(:project => Project.find(3))
68 69 assert !repository.save
69 70
70 71 repository.url = "svn://localhost"
71 72 assert repository.save
72 73 repository.reload
73 74
74 75 project = Project.find(3)
75 76 assert_equal repository, project.repository
76 77 end
77 78
78 79 def test_first_repository_should_be_set_as_default
79 80 repository1 = Repository::Subversion.new(
80 81 :project => Project.find(3),
81 82 :identifier => 'svn1',
82 83 :url => 'file:///svn1'
83 84 )
84 85 assert repository1.save
85 86 assert repository1.is_default?
86 87
87 88 repository2 = Repository::Subversion.new(
88 89 :project => Project.find(3),
89 90 :identifier => 'svn2',
90 91 :url => 'file:///svn2'
91 92 )
92 93 assert repository2.save
93 94 assert !repository2.is_default?
94 95
95 96 assert_equal repository1, Project.find(3).repository
96 97 assert_equal [repository1, repository2], Project.find(3).repositories.sort
97 98 end
98 99
99 100 def test_default_repository_should_be_one
100 101 assert_equal 0, Project.find(3).repositories.count
101 102 repository1 = Repository::Subversion.new(
102 103 :project => Project.find(3),
103 104 :identifier => 'svn1',
104 105 :url => 'file:///svn1'
105 106 )
106 107 assert repository1.save
107 108 assert repository1.is_default?
108 109
109 110 repository2 = Repository::Subversion.new(
110 111 :project => Project.find(3),
111 112 :identifier => 'svn2',
112 113 :url => 'file:///svn2',
113 114 :is_default => true
114 115 )
115 116 assert repository2.save
116 117 assert repository2.is_default?
117 118 repository1.reload
118 119 assert !repository1.is_default?
119 120
120 121 assert_equal repository2, Project.find(3).repository
121 122 assert_equal [repository2, repository1], Project.find(3).repositories.sort
122 123 end
123 124
124 125 def test_identifier_should_accept_letters_digits_dashes_and_underscores
125 126 r = Repository::Subversion.new(
126 127 :project_id => 3,
127 128 :identifier => 'svn-123_45',
128 129 :url => 'file:///svn'
129 130 )
130 131 assert r.save
131 132 end
132 133
133 134 def test_identifier_should_not_be_frozen_for_a_new_repository
134 135 assert_equal false, Repository.new.identifier_frozen?
135 136 end
136 137
137 138 def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier
138 139 Repository.where(:id => 10).update_all(["identifier = ''"])
139 140 assert_equal false, Repository.find(10).identifier_frozen?
140 141 end
141 142
142 143 def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
143 144 Repository.where(:id => 10).update_all(["identifier = 'abc123'"])
144 145 assert_equal true, Repository.find(10).identifier_frozen?
145 146 end
146 147
147 148 def test_identifier_should_not_accept_change_if_frozen
148 149 r = Repository.new(:identifier => 'foo')
149 150 r.stubs(:identifier_frozen?).returns(true)
150 151
151 152 r.identifier = 'bar'
152 153 assert_equal 'foo', r.identifier
153 154 end
154 155
155 156 def test_identifier_should_accept_change_if_not_frozen
156 157 r = Repository.new(:identifier => 'foo')
157 158 r.stubs(:identifier_frozen?).returns(false)
158 159
159 160 r.identifier = 'bar'
160 161 assert_equal 'bar', r.identifier
161 162 end
162 163
163 164 def test_destroy
164 165 repository = Repository.find(10)
165 166 changesets = repository.changesets.count
166 167 changes = repository.filechanges.count
167 168
168 169 assert_difference 'Changeset.count', -changesets do
169 170 assert_difference 'Change.count', -changes do
170 171 Repository.find(10).destroy
171 172 end
172 173 end
173 174 end
174 175
175 176 def test_destroy_should_delete_parents_associations
176 177 changeset = Changeset.find(102)
177 178 changeset.parents = Changeset.where(:id => [100, 101]).to_a
178 179 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").count', -2 do
179 180 Repository.find(10).destroy
180 181 end
181 182 end
182 183
183 184 def test_destroy_should_delete_issues_associations
184 185 changeset = Changeset.find(102)
185 186 changeset.issues = Issue.where(:id => [1, 2]).to_a
186 187 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").count', -2 do
187 188 Repository.find(10).destroy
188 189 end
189 190 end
190 191
191 192 def test_should_not_create_with_disabled_scm
192 193 # disable Subversion
193 194 with_settings :enabled_scm => ['Darcs', 'Git'] do
194 195 repository = Repository::Subversion.new(
195 196 :project => Project.find(3), :url => "svn://localhost")
196 197 assert !repository.save
197 198 assert_include I18n.translate('activerecord.errors.messages.invalid'),
198 199 repository.errors[:type]
199 200 end
200 201 end
201 202
202 203 def test_scan_changesets_for_issue_ids
203 204 Setting.default_language = 'en'
204 205 Setting.commit_ref_keywords = 'refs , references, IssueID'
205 206 Setting.commit_update_keywords = [
206 207 {'keywords' => 'fixes , closes',
207 208 'status_id' => IssueStatus.where(:is_closed => true).first.id,
208 209 'done_ratio' => '90'}
209 210 ]
210 211 Setting.default_language = 'en'
211 212 ActionMailer::Base.deliveries.clear
212 213
213 214 # make sure issue 1 is not already closed
214 215 fixed_issue = Issue.find(1)
215 216 assert !fixed_issue.closed?
216 217 old_status = fixed_issue.status
217 218
218 219 with_settings :notified_events => %w(issue_added issue_updated) do
219 220 Repository.scan_changesets_for_issue_ids
220 221 end
221 222 assert_equal [101, 102], Issue.find(3).changeset_ids
222 223
223 224 # fixed issues
224 225 fixed_issue.reload
225 226 assert fixed_issue.closed?
226 227 assert_equal 90, fixed_issue.done_ratio
227 228 assert_equal [101], fixed_issue.changeset_ids
228 229
229 230 # issue change
230 231 journal = fixed_issue.journals.reorder('created_on desc').first
231 232 assert_equal User.find_by_login('dlopper'), journal.user
232 233 assert_equal 'Applied in changeset r2.', journal.notes
233 234
234 235 # 2 email notifications
235 236 assert_equal 2, ActionMailer::Base.deliveries.size
236 237 mail = ActionMailer::Base.deliveries.first
237 238 assert_not_nil mail
238 239 assert mail.subject.starts_with?(
239 240 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
240 241 assert_mail_body_match(
241 242 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
242 243
243 244 # ignoring commits referencing an issue of another project
244 245 assert_equal [], Issue.find(4).changesets
245 246 end
246 247
247 248 def test_for_changeset_comments_strip
248 249 repository = Repository::Mercurial.create(
249 250 :project => Project.find( 4 ),
250 251 :url => '/foo/bar/baz' )
251 252 comment = <<-COMMENT
252 253 This is a loooooooooooooooooooooooooooong comment
253 254
254 255
255 256 COMMENT
256 257 changeset = Changeset.new(
257 258 :comments => comment, :commit_date => Time.now,
258 259 :revision => 0, :scmid => 'f39b7922fb3c',
259 260 :committer => 'foo <foo@example.com>',
260 261 :committed_on => Time.now, :repository => repository )
261 262 assert( changeset.save )
262 263 assert_not_equal( comment, changeset.comments )
263 264 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
264 265 changeset.comments )
265 266 end
266 267
267 268 def test_for_urls_strip_cvs
268 269 repository = Repository::Cvs.create(
269 270 :project => Project.find(4),
270 271 :url => ' :pserver:login:password@host:/path/to/the/repository',
271 272 :root_url => 'foo ',
272 273 :log_encoding => 'UTF-8')
273 274 assert repository.save
274 275 repository.reload
275 276 assert_equal ':pserver:login:password@host:/path/to/the/repository',
276 277 repository.url
277 278 assert_equal 'foo', repository.root_url
278 279 end
279 280
280 281 def test_for_urls_strip_subversion
281 282 repository = Repository::Subversion.create(
282 283 :project => Project.find(4),
283 284 :url => ' file:///dummy ')
284 285 assert repository.save
285 286 repository.reload
286 287 assert_equal 'file:///dummy', repository.url
287 288 end
288 289
289 290 def test_for_urls_strip_git
290 291 repository = Repository::Git.create(
291 292 :project => Project.find(4),
292 293 :url => ' c:\dummy ')
293 294 assert repository.save
294 295 repository.reload
295 296 assert_equal 'c:\dummy', repository.url
296 297 end
297 298
298 299 def test_manual_user_mapping
299 300 assert_no_difference "Changeset.where('user_id <> 2').count" do
300 301 c = Changeset.create!(
301 302 :repository => @repository,
302 303 :committer => 'foo',
303 304 :committed_on => Time.now,
304 305 :revision => 100,
305 306 :comments => 'Committed by foo.'
306 307 )
307 308 assert_nil c.user
308 309 @repository.committer_ids = {'foo' => '2'}
309 310 assert_equal User.find(2), c.reload.user
310 311 # committer is now mapped
311 312 c = Changeset.create!(
312 313 :repository => @repository,
313 314 :committer => 'foo',
314 315 :committed_on => Time.now,
315 316 :revision => 101,
316 317 :comments => 'Another commit by foo.'
317 318 )
318 319 assert_equal User.find(2), c.user
319 320 end
320 321 end
321 322
322 323 def test_auto_user_mapping_by_username
323 324 c = Changeset.create!(
324 325 :repository => @repository,
325 326 :committer => 'jsmith',
326 327 :committed_on => Time.now,
327 328 :revision => 100,
328 329 :comments => 'Committed by john.'
329 330 )
330 331 assert_equal User.find(2), c.user
331 332 end
332 333
333 334 def test_auto_user_mapping_by_email
334 335 c = Changeset.create!(
335 336 :repository => @repository,
336 337 :committer => 'john <jsmith@somenet.foo>',
337 338 :committed_on => Time.now,
338 339 :revision => 100,
339 340 :comments => 'Committed by john.'
340 341 )
341 342 assert_equal User.find(2), c.user
342 343 end
343 344
344 345 def test_filesystem_avaialbe
345 346 klass = Repository::Filesystem
346 347 assert klass.scm_adapter_class
347 348 assert_equal true, klass.scm_available
348 349 end
349 350
350 351 def test_extra_info_should_not_return_non_hash_value
351 352 repo = Repository.new
352 353 repo.extra_info = "foo"
353 354 assert_nil repo.extra_info
354 355 end
355 356
356 357 def test_merge_extra_info
357 358 repo = Repository::Subversion.new(:project => Project.find(3))
358 359 assert !repo.save
359 360 repo.url = "svn://localhost"
360 361 assert repo.save
361 362 repo.reload
362 363 project = Project.find(3)
363 364 assert_equal repo, project.repository
364 365 assert_nil repo.extra_info
365 366 h1 = {"test_1" => {"test_11" => "test_value_11"}}
366 367 repo.merge_extra_info(h1)
367 368 assert_equal h1, repo.extra_info
368 369 h2 = {"test_2" => {
369 370 "test_21" => "test_value_21",
370 371 "test_22" => "test_value_22",
371 372 }}
372 373 repo.merge_extra_info(h2)
373 374 assert_equal (h = {"test_11" => "test_value_11"}),
374 375 repo.extra_info["test_1"]
375 376 assert_equal "test_value_21",
376 377 repo.extra_info["test_2"]["test_21"]
377 378 h3 = {"test_2" => {
378 379 "test_23" => "test_value_23",
379 380 "test_24" => "test_value_24",
380 381 }}
381 382 repo.merge_extra_info(h3)
382 383 assert_equal (h = {"test_11" => "test_value_11"}),
383 384 repo.extra_info["test_1"]
384 385 assert_nil repo.extra_info["test_2"]["test_21"]
385 386 assert_equal "test_value_23",
386 387 repo.extra_info["test_2"]["test_23"]
387 388 end
388 389
389 390 def test_sort_should_not_raise_an_error_with_nil_identifiers
390 391 r1 = Repository.new
391 392 r2 = Repository.new
392 393
393 394 assert_nothing_raised do
394 395 [r1, r2].sort
395 396 end
396 397 end
397 398
398 399 def test_stats_by_author_reflect_changesets_and_changes
399 400 repository = Repository.find(10)
400 401
401 402 expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
402 403 assert_equal expected, repository.stats_by_author
403 404
404 405 set = Changeset.create!(
405 406 :repository => repository,
406 407 :committer => 'dlopper',
407 408 :committed_on => Time.now,
408 409 :revision => 101,
409 410 :comments => 'Another commit by foo.'
410 411 )
411 412 Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1')
412 413 Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2')
413 414 expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}}
414 415 assert_equal expected, repository.stats_by_author
415 416 end
416 417
417 418 def test_stats_by_author_honnor_committers
418 419 # in fact it is really tested above, but let's have a dedicated test
419 420 # to ensure things are dynamically linked to Users
420 421 User.find_by_login("dlopper").update_attribute(:firstname, "Dave's")
421 422 repository = Repository.find(10)
422 423 expected = {"Dave's Lopper"=>{:commits_count=>10, :changes_count=>3}}
423 424 assert_equal expected, repository.stats_by_author
424 425 end
425 426
426 427 def test_stats_by_author_doesnt_drop_unmapped_users
427 428 repository = Repository.find(10)
428 429 Changeset.create!(
429 430 :repository => repository,
430 431 :committer => 'unnamed <foo@bar.net>',
431 432 :committed_on => Time.now,
432 433 :revision => 101,
433 434 :comments => 'Another commit by foo.'
434 435 )
435 436
436 437 assert repository.stats_by_author.has_key?("unnamed <foo@bar.net>")
437 438 end
438 439
439 440 def test_stats_by_author_merge_correctly
440 441 # as we honnor users->committer map and it's not injective,
441 442 # we must be sure merges happen correctly and stats are not
442 443 # wiped out when two source counts map to the same user.
443 444 #
444 445 # Here we have Changeset's with committer="dlopper" and others
445 446 # with committer="dlopper <dlopper@somefoo.net>"
446 447 repository = Repository.find(10)
447 448
448 449 expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
449 450 assert_equal expected, repository.stats_by_author
450 451
451 452 set = Changeset.create!(
452 453 :repository => repository,
453 454 :committer => 'dlopper <dlopper@somefoo.net>',
454 455 :committed_on => Time.now,
455 456 :revision => 101,
456 457 :comments => 'Another commit by foo.'
457 458 )
458 459
459 460 expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
460 461 assert_equal expected, repository.stats_by_author
461 462 end
462 463
463 464 def test_fetch_changesets
464 465 # 2 repositories in fixtures
465 466 Repository::Subversion.any_instance.expects(:fetch_changesets).twice.returns(true)
466 467 Repository.fetch_changesets
467 468 end
468 469 end
General Comments 0
You need to be logged in to leave comments. Login now