@@ -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&t=z&s=">http://foo.bar/page?p=1&t=z&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"bar">http://foo"bar</a>', |
|
89 | 90 | # wrap in angle brackets |
|
90 | 91 | '<http://foo.bar>' => '<<a class="external" href="http://foo.bar">http://foo.bar</a>>', |
|
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 "title"" alt="This is a double-quoted "title"" />', |
|
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 "double-quotes"" 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"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}: <&>", 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}: <&>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 & markups</strong>]]' => |
|
665 | 666 | link_to("With title containing <strong>HTML entities & markups</strong>".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><div>content</div></p>", |
|
877 | 878 | "<div class=\"bold\">content</div>" => "<p><div class=\"bold\">content</div></p>", |
|
878 | 879 | "<script>some script;</script>" => "<p><script>some script;</script></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><div>content</div></pre>", |
|
883 | 884 | "HTML comment: <!-- no comments -->" => "<p>HTML comment: <!-- no comments --></p>", |
|
884 | 885 | "<!-- opening comment" => "<p><!-- 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 <tag>a tag</tag>" |
|
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 | <prepared-statement-cache-size>32</prepared-statement-cache-size> |
|
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">¶</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">¶</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">¶</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">¶</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">¶</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"><< me >></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 » 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