##// END OF EJS Templates
Use User#generate_password in MailHandler....
Jean-Philippe Lang -
r11227:e14caf8e33fb
parent child
Show More
@@ -1,498 +1,496
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 class MailHandler < ActionMailer::Base
19 19 include ActionView::Helpers::SanitizeHelper
20 20 include Redmine::I18n
21 21
22 22 class UnauthorizedAction < StandardError; end
23 23 class MissingInformation < StandardError; end
24 24
25 25 attr_reader :email, :user
26 26
27 27 def self.receive(email, options={})
28 28 @@handler_options = options.dup
29 29
30 30 @@handler_options[:issue] ||= {}
31 31
32 32 if @@handler_options[:allow_override].is_a?(String)
33 33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
34 34 end
35 35 @@handler_options[:allow_override] ||= []
36 36 # Project needs to be overridable if not specified
37 37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
38 38 # Status overridable by default
39 39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
40 40
41 41 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
42 42
43 43 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
44 44 super(email)
45 45 end
46 46
47 47 def logger
48 48 Rails.logger
49 49 end
50 50
51 51 cattr_accessor :ignored_emails_headers
52 52 @@ignored_emails_headers = {
53 53 'X-Auto-Response-Suppress' => 'oof',
54 54 'Auto-Submitted' => /^auto-/
55 55 }
56 56
57 57 # Processes incoming emails
58 58 # Returns the created object (eg. an issue, a message) or false
59 59 def receive(email)
60 60 @email = email
61 61 sender_email = email.from.to_a.first.to_s.strip
62 62 # Ignore emails received from the application emission address to avoid hell cycles
63 63 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
64 64 if logger && logger.info
65 65 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
66 66 end
67 67 return false
68 68 end
69 69 # Ignore auto generated emails
70 70 self.class.ignored_emails_headers.each do |key, ignored_value|
71 71 value = email.header[key]
72 72 if value
73 73 value = value.to_s.downcase
74 74 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
75 75 if logger && logger.info
76 76 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
77 77 end
78 78 return false
79 79 end
80 80 end
81 81 end
82 82 @user = User.find_by_mail(sender_email) if sender_email.present?
83 83 if @user && !@user.active?
84 84 if logger && logger.info
85 85 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
86 86 end
87 87 return false
88 88 end
89 89 if @user.nil?
90 90 # Email was submitted by an unknown user
91 91 case @@handler_options[:unknown_user]
92 92 when 'accept'
93 93 @user = User.anonymous
94 94 when 'create'
95 95 @user = create_user_from_email
96 96 if @user
97 97 if logger && logger.info
98 98 logger.info "MailHandler: [#{@user.login}] account created"
99 99 end
100 100 Mailer.account_information(@user, @user.password).deliver
101 101 else
102 102 if logger && logger.error
103 103 logger.error "MailHandler: could not create account for [#{sender_email}]"
104 104 end
105 105 return false
106 106 end
107 107 else
108 108 # Default behaviour, emails from unknown users are ignored
109 109 if logger && logger.info
110 110 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
111 111 end
112 112 return false
113 113 end
114 114 end
115 115 User.current = @user
116 116 dispatch
117 117 end
118 118
119 119 private
120 120
121 121 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
122 122 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
123 123 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
124 124
125 125 def dispatch
126 126 headers = [email.in_reply_to, email.references].flatten.compact
127 127 subject = email.subject.to_s
128 128 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
129 129 klass, object_id = $1, $2.to_i
130 130 method_name = "receive_#{klass}_reply"
131 131 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
132 132 send method_name, object_id
133 133 else
134 134 # ignoring it
135 135 end
136 136 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
137 137 receive_issue_reply(m[1].to_i)
138 138 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
139 139 receive_message_reply(m[1].to_i)
140 140 else
141 141 dispatch_to_default
142 142 end
143 143 rescue ActiveRecord::RecordInvalid => e
144 144 # TODO: send a email to the user
145 145 logger.error e.message if logger
146 146 false
147 147 rescue MissingInformation => e
148 148 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
149 149 false
150 150 rescue UnauthorizedAction => e
151 151 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
152 152 false
153 153 end
154 154
155 155 def dispatch_to_default
156 156 receive_issue
157 157 end
158 158
159 159 # Creates a new issue
160 160 def receive_issue
161 161 project = target_project
162 162 # check permission
163 163 unless @@handler_options[:no_permission_check]
164 164 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
165 165 end
166 166
167 167 issue = Issue.new(:author => user, :project => project)
168 168 issue.safe_attributes = issue_attributes_from_keywords(issue)
169 169 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
170 170 issue.subject = cleaned_up_subject
171 171 if issue.subject.blank?
172 172 issue.subject = '(no subject)'
173 173 end
174 174 issue.description = cleaned_up_text_body
175 175
176 176 # add To and Cc as watchers before saving so the watchers can reply to Redmine
177 177 add_watchers(issue)
178 178 issue.save!
179 179 add_attachments(issue)
180 180 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
181 181 issue
182 182 end
183 183
184 184 # Adds a note to an existing issue
185 185 def receive_issue_reply(issue_id, from_journal=nil)
186 186 issue = Issue.find_by_id(issue_id)
187 187 return unless issue
188 188 # check permission
189 189 unless @@handler_options[:no_permission_check]
190 190 unless user.allowed_to?(:add_issue_notes, issue.project) ||
191 191 user.allowed_to?(:edit_issues, issue.project)
192 192 raise UnauthorizedAction
193 193 end
194 194 end
195 195
196 196 # ignore CLI-supplied defaults for new issues
197 197 @@handler_options[:issue].clear
198 198
199 199 journal = issue.init_journal(user)
200 200 if from_journal && from_journal.private_notes?
201 201 # If the received email was a reply to a private note, make the added note private
202 202 issue.private_notes = true
203 203 end
204 204 issue.safe_attributes = issue_attributes_from_keywords(issue)
205 205 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
206 206 journal.notes = cleaned_up_text_body
207 207 add_attachments(issue)
208 208 issue.save!
209 209 if logger && logger.info
210 210 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
211 211 end
212 212 journal
213 213 end
214 214
215 215 # Reply will be added to the issue
216 216 def receive_journal_reply(journal_id)
217 217 journal = Journal.find_by_id(journal_id)
218 218 if journal && journal.journalized_type == 'Issue'
219 219 receive_issue_reply(journal.journalized_id, journal)
220 220 end
221 221 end
222 222
223 223 # Receives a reply to a forum message
224 224 def receive_message_reply(message_id)
225 225 message = Message.find_by_id(message_id)
226 226 if message
227 227 message = message.root
228 228
229 229 unless @@handler_options[:no_permission_check]
230 230 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
231 231 end
232 232
233 233 if !message.locked?
234 234 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
235 235 :content => cleaned_up_text_body)
236 236 reply.author = user
237 237 reply.board = message.board
238 238 message.children << reply
239 239 add_attachments(reply)
240 240 reply
241 241 else
242 242 if logger && logger.info
243 243 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
244 244 end
245 245 end
246 246 end
247 247 end
248 248
249 249 def add_attachments(obj)
250 250 if email.attachments && email.attachments.any?
251 251 email.attachments.each do |attachment|
252 252 filename = attachment.filename
253 253 unless filename.respond_to?(:encoding)
254 254 # try to reencode to utf8 manually with ruby1.8
255 255 h = attachment.header['Content-Disposition']
256 256 unless h.nil?
257 257 begin
258 258 if m = h.value.match(/filename\*[0-9\*]*=([^=']+)'/)
259 259 filename = Redmine::CodesetUtil.to_utf8(filename, m[1])
260 260 elsif m = h.value.match(/filename=.*=\?([^\?]+)\?[BbQq]\?/)
261 261 # http://tools.ietf.org/html/rfc2047#section-4
262 262 filename = Redmine::CodesetUtil.to_utf8(filename, m[1])
263 263 end
264 264 rescue
265 265 # nop
266 266 end
267 267 end
268 268 end
269 269 obj.attachments << Attachment.create(:container => obj,
270 270 :file => attachment.decoded,
271 271 :filename => filename,
272 272 :author => user,
273 273 :content_type => attachment.mime_type)
274 274 end
275 275 end
276 276 end
277 277
278 278 # Adds To and Cc as watchers of the given object if the sender has the
279 279 # appropriate permission
280 280 def add_watchers(obj)
281 281 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
282 282 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
283 283 unless addresses.empty?
284 284 watchers = User.active.where('LOWER(mail) IN (?)', addresses).all
285 285 watchers.each {|w| obj.add_watcher(w)}
286 286 end
287 287 end
288 288 end
289 289
290 290 def get_keyword(attr, options={})
291 291 @keywords ||= {}
292 292 if @keywords.has_key?(attr)
293 293 @keywords[attr]
294 294 else
295 295 @keywords[attr] = begin
296 296 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
297 297 (v = extract_keyword!(plain_text_body, attr, options[:format]))
298 298 v
299 299 elsif !@@handler_options[:issue][attr].blank?
300 300 @@handler_options[:issue][attr]
301 301 end
302 302 end
303 303 end
304 304 end
305 305
306 306 # Destructively extracts the value for +attr+ in +text+
307 307 # Returns nil if no matching keyword found
308 308 def extract_keyword!(text, attr, format=nil)
309 309 keys = [attr.to_s.humanize]
310 310 if attr.is_a?(Symbol)
311 311 if user && user.language.present?
312 312 keys << l("field_#{attr}", :default => '', :locale => user.language)
313 313 end
314 314 if Setting.default_language.present?
315 315 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
316 316 end
317 317 end
318 318 keys.reject! {|k| k.blank?}
319 319 keys.collect! {|k| Regexp.escape(k)}
320 320 format ||= '.+'
321 321 keyword = nil
322 322 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
323 323 if m = text.match(regexp)
324 324 keyword = m[2].strip
325 325 text.gsub!(regexp, '')
326 326 end
327 327 keyword
328 328 end
329 329
330 330 def target_project
331 331 # TODO: other ways to specify project:
332 332 # * parse the email To field
333 333 # * specific project (eg. Setting.mail_handler_target_project)
334 334 target = Project.find_by_identifier(get_keyword(:project))
335 335 raise MissingInformation.new('Unable to determine target project') if target.nil?
336 336 target
337 337 end
338 338
339 339 # Returns a Hash of issue attributes extracted from keywords in the email body
340 340 def issue_attributes_from_keywords(issue)
341 341 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
342 342
343 343 attrs = {
344 344 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
345 345 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
346 346 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
347 347 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
348 348 'assigned_to_id' => assigned_to.try(:id),
349 349 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
350 350 issue.project.shared_versions.named(k).first.try(:id),
351 351 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
352 352 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
353 353 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
354 354 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
355 355 }.delete_if {|k, v| v.blank? }
356 356
357 357 if issue.new_record? && attrs['tracker_id'].nil?
358 358 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
359 359 end
360 360
361 361 attrs
362 362 end
363 363
364 364 # Returns a Hash of issue custom field values extracted from keywords in the email body
365 365 def custom_field_values_from_keywords(customized)
366 366 customized.custom_field_values.inject({}) do |h, v|
367 367 if keyword = get_keyword(v.custom_field.name, :override => true)
368 368 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
369 369 end
370 370 h
371 371 end
372 372 end
373 373
374 374 # Returns the text/plain part of the email
375 375 # If not found (eg. HTML-only email), returns the body with tags removed
376 376 def plain_text_body
377 377 return @plain_text_body unless @plain_text_body.nil?
378 378
379 379 part = email.text_part || email.html_part || email
380 380 @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset)
381 381
382 382 # strip html tags and remove doctype directive
383 383 @plain_text_body = strip_tags(@plain_text_body.strip)
384 384 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
385 385 @plain_text_body
386 386 end
387 387
388 388 def cleaned_up_text_body
389 389 cleanup_body(plain_text_body)
390 390 end
391 391
392 392 def cleaned_up_subject
393 393 subject = email.subject.to_s
394 394 unless subject.respond_to?(:encoding)
395 395 # try to reencode to utf8 manually with ruby1.8
396 396 begin
397 397 if h = email.header[:subject]
398 398 # http://tools.ietf.org/html/rfc2047#section-4
399 399 if m = h.value.match(/=\?([^\?]+)\?[BbQq]\?/)
400 400 subject = Redmine::CodesetUtil.to_utf8(subject, m[1])
401 401 end
402 402 end
403 403 rescue
404 404 # nop
405 405 end
406 406 end
407 407 subject.strip[0,255]
408 408 end
409 409
410 410 def self.full_sanitizer
411 411 @full_sanitizer ||= HTML::FullSanitizer.new
412 412 end
413 413
414 414 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
415 415 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
416 416 value = value.to_s.slice(0, limit)
417 417 object.send("#{attribute}=", value)
418 418 end
419 419
420 420 # Returns a User from an email address and a full name
421 421 def self.new_user_from_attributes(email_address, fullname=nil)
422 422 user = User.new
423 423
424 424 # Truncating the email address would result in an invalid format
425 425 user.mail = email_address
426 426 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
427 427
428 428 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
429 429 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
430 430 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
431 431 user.lastname = '-' if user.lastname.blank?
432
433 password_length = [Setting.password_min_length.to_i, 10].max
434 user.password = Redmine::Utils.random_hex(password_length / 2 + 1)
435 432 user.language = Setting.default_language
433 user.generate_password = true
436 434
437 435 unless user.valid?
438 436 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
439 437 user.firstname = "-" unless user.errors[:firstname].blank?
440 438 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
441 439 end
442 440
443 441 user
444 442 end
445 443
446 444 # Creates a User for the +email+ sender
447 445 # Returns the user or nil if it could not be created
448 446 def create_user_from_email
449 447 from = email.header['from'].to_s
450 448 addr, name = from, nil
451 449 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
452 450 addr, name = m[2], m[1]
453 451 end
454 452 if addr.present?
455 453 user = self.class.new_user_from_attributes(addr, name)
456 454 if user.save
457 455 user
458 456 else
459 457 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
460 458 nil
461 459 end
462 460 else
463 461 logger.error "MailHandler: failed to create User: no FROM address found" if logger
464 462 nil
465 463 end
466 464 end
467 465
468 466 # Removes the email body of text after the truncation configurations.
469 467 def cleanup_body(body)
470 468 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
471 469 unless delimiters.empty?
472 470 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
473 471 body = body.gsub(regex, '')
474 472 end
475 473 body.strip
476 474 end
477 475
478 476 def find_assignee_from_keyword(keyword, issue)
479 477 keyword = keyword.to_s.downcase
480 478 assignable = issue.assignable_users
481 479 assignee = nil
482 480 assignee ||= assignable.detect {|a|
483 481 a.mail.to_s.downcase == keyword ||
484 482 a.login.to_s.downcase == keyword
485 483 }
486 484 if assignee.nil? && keyword.match(/ /)
487 485 firstname, lastname = *(keyword.split) # "First Last Throwaway"
488 486 assignee ||= assignable.detect {|a|
489 487 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
490 488 a.lastname.to_s.downcase == lastname
491 489 }
492 490 end
493 491 if assignee.nil?
494 492 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
495 493 end
496 494 assignee
497 495 end
498 496 end
@@ -1,776 +1,768
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 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 25 :issues, :issue_statuses,
26 26 :workflows, :trackers, :projects_trackers,
27 27 :versions, :enumerations, :issue_categories,
28 28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 29 :boards, :messages
30 30
31 31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32 32
33 33 def setup
34 34 ActionMailer::Base.deliveries.clear
35 35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 36 end
37 37
38 38 def teardown
39 39 Setting.clear_cache
40 40 end
41 41
42 42 def test_add_issue
43 43 ActionMailer::Base.deliveries.clear
44 44 # This email contains: 'Project: onlinestore'
45 45 issue = submit_email('ticket_on_given_project.eml')
46 46 assert issue.is_a?(Issue)
47 47 assert !issue.new_record?
48 48 issue.reload
49 49 assert_equal Project.find(2), issue.project
50 50 assert_equal issue.project.trackers.first, issue.tracker
51 51 assert_equal 'New ticket on a given project', issue.subject
52 52 assert_equal User.find_by_login('jsmith'), issue.author
53 53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 55 assert_equal '2010-01-01', issue.start_date.to_s
56 56 assert_equal '2010-12-31', issue.due_date.to_s
57 57 assert_equal User.find_by_login('jsmith'), issue.assigned_to
58 58 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
59 59 assert_equal 2.5, issue.estimated_hours
60 60 assert_equal 30, issue.done_ratio
61 61 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
62 62 # keywords should be removed from the email body
63 63 assert !issue.description.match(/^Project:/i)
64 64 assert !issue.description.match(/^Status:/i)
65 65 assert !issue.description.match(/^Start Date:/i)
66 66 # Email notification should be sent
67 67 mail = ActionMailer::Base.deliveries.last
68 68 assert_not_nil mail
69 69 assert mail.subject.include?('New ticket on a given project')
70 70 end
71 71
72 72 def test_add_issue_with_default_tracker
73 73 # This email contains: 'Project: onlinestore'
74 74 issue = submit_email(
75 75 'ticket_on_given_project.eml',
76 76 :issue => {:tracker => 'Support request'}
77 77 )
78 78 assert issue.is_a?(Issue)
79 79 assert !issue.new_record?
80 80 issue.reload
81 81 assert_equal 'Support request', issue.tracker.name
82 82 end
83 83
84 84 def test_add_issue_with_status
85 85 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
86 86 issue = submit_email('ticket_on_given_project.eml')
87 87 assert issue.is_a?(Issue)
88 88 assert !issue.new_record?
89 89 issue.reload
90 90 assert_equal Project.find(2), issue.project
91 91 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
92 92 end
93 93
94 94 def test_add_issue_with_attributes_override
95 95 issue = submit_email(
96 96 'ticket_with_attributes.eml',
97 97 :allow_override => 'tracker,category,priority'
98 98 )
99 99 assert issue.is_a?(Issue)
100 100 assert !issue.new_record?
101 101 issue.reload
102 102 assert_equal 'New ticket on a given project', issue.subject
103 103 assert_equal User.find_by_login('jsmith'), issue.author
104 104 assert_equal Project.find(2), issue.project
105 105 assert_equal 'Feature request', issue.tracker.to_s
106 106 assert_equal 'Stock management', issue.category.to_s
107 107 assert_equal 'Urgent', issue.priority.to_s
108 108 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
109 109 end
110 110
111 111 def test_add_issue_with_group_assignment
112 112 with_settings :issue_group_assignment => '1' do
113 113 issue = submit_email('ticket_on_given_project.eml') do |email|
114 114 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
115 115 end
116 116 assert issue.is_a?(Issue)
117 117 assert !issue.new_record?
118 118 issue.reload
119 119 assert_equal Group.find(11), issue.assigned_to
120 120 end
121 121 end
122 122
123 123 def test_add_issue_with_partial_attributes_override
124 124 issue = submit_email(
125 125 'ticket_with_attributes.eml',
126 126 :issue => {:priority => 'High'},
127 127 :allow_override => ['tracker']
128 128 )
129 129 assert issue.is_a?(Issue)
130 130 assert !issue.new_record?
131 131 issue.reload
132 132 assert_equal 'New ticket on a given project', issue.subject
133 133 assert_equal User.find_by_login('jsmith'), issue.author
134 134 assert_equal Project.find(2), issue.project
135 135 assert_equal 'Feature request', issue.tracker.to_s
136 136 assert_nil issue.category
137 137 assert_equal 'High', issue.priority.to_s
138 138 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
139 139 end
140 140
141 141 def test_add_issue_with_spaces_between_attribute_and_separator
142 142 issue = submit_email(
143 143 'ticket_with_spaces_between_attribute_and_separator.eml',
144 144 :allow_override => 'tracker,category,priority'
145 145 )
146 146 assert issue.is_a?(Issue)
147 147 assert !issue.new_record?
148 148 issue.reload
149 149 assert_equal 'New ticket on a given project', issue.subject
150 150 assert_equal User.find_by_login('jsmith'), issue.author
151 151 assert_equal Project.find(2), issue.project
152 152 assert_equal 'Feature request', issue.tracker.to_s
153 153 assert_equal 'Stock management', issue.category.to_s
154 154 assert_equal 'Urgent', issue.priority.to_s
155 155 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
156 156 end
157 157
158 158 def test_add_issue_with_attachment_to_specific_project
159 159 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
160 160 assert issue.is_a?(Issue)
161 161 assert !issue.new_record?
162 162 issue.reload
163 163 assert_equal 'Ticket created by email with attachment', issue.subject
164 164 assert_equal User.find_by_login('jsmith'), issue.author
165 165 assert_equal Project.find(2), issue.project
166 166 assert_equal 'This is a new ticket with attachments', issue.description
167 167 # Attachment properties
168 168 assert_equal 1, issue.attachments.size
169 169 assert_equal 'Paella.jpg', issue.attachments.first.filename
170 170 assert_equal 'image/jpeg', issue.attachments.first.content_type
171 171 assert_equal 10790, issue.attachments.first.filesize
172 172 end
173 173
174 174 def test_add_issue_with_custom_fields
175 175 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
176 176 assert issue.is_a?(Issue)
177 177 assert !issue.new_record?
178 178 issue.reload
179 179 assert_equal 'New ticket with custom field values', issue.subject
180 180 assert_equal 'PostgreSQL', issue.custom_field_value(1)
181 181 assert_equal 'Value for a custom field', issue.custom_field_value(2)
182 182 assert !issue.description.match(/^searchable field:/i)
183 183 end
184 184
185 185 def test_add_issue_with_version_custom_fields
186 186 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
187 187
188 188 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
189 189 email << "Affected version: 1.0\n"
190 190 end
191 191 assert issue.is_a?(Issue)
192 192 assert !issue.new_record?
193 193 issue.reload
194 194 assert_equal '2', issue.custom_field_value(field)
195 195 end
196 196
197 197 def test_add_issue_should_match_assignee_on_display_name
198 198 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
199 199 User.add_to_project(user, Project.find(2))
200 200 issue = submit_email('ticket_on_given_project.eml') do |email|
201 201 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
202 202 end
203 203 assert issue.is_a?(Issue)
204 204 assert_equal user, issue.assigned_to
205 205 end
206 206
207 207 def test_add_issue_with_cc
208 208 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
209 209 assert issue.is_a?(Issue)
210 210 assert !issue.new_record?
211 211 issue.reload
212 212 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
213 213 assert_equal 1, issue.watcher_user_ids.size
214 214 end
215 215
216 216 def test_add_issue_by_unknown_user
217 217 assert_no_difference 'User.count' do
218 218 assert_equal false,
219 219 submit_email(
220 220 'ticket_by_unknown_user.eml',
221 221 :issue => {:project => 'ecookbook'}
222 222 )
223 223 end
224 224 end
225 225
226 226 def test_add_issue_by_anonymous_user
227 227 Role.anonymous.add_permission!(:add_issues)
228 228 assert_no_difference 'User.count' do
229 229 issue = submit_email(
230 230 'ticket_by_unknown_user.eml',
231 231 :issue => {:project => 'ecookbook'},
232 232 :unknown_user => 'accept'
233 233 )
234 234 assert issue.is_a?(Issue)
235 235 assert issue.author.anonymous?
236 236 end
237 237 end
238 238
239 239 def test_add_issue_by_anonymous_user_with_no_from_address
240 240 Role.anonymous.add_permission!(:add_issues)
241 241 assert_no_difference 'User.count' do
242 242 issue = submit_email(
243 243 'ticket_by_empty_user.eml',
244 244 :issue => {:project => 'ecookbook'},
245 245 :unknown_user => 'accept'
246 246 )
247 247 assert issue.is_a?(Issue)
248 248 assert issue.author.anonymous?
249 249 end
250 250 end
251 251
252 252 def test_add_issue_by_anonymous_user_on_private_project
253 253 Role.anonymous.add_permission!(:add_issues)
254 254 assert_no_difference 'User.count' do
255 255 assert_no_difference 'Issue.count' do
256 256 assert_equal false,
257 257 submit_email(
258 258 'ticket_by_unknown_user.eml',
259 259 :issue => {:project => 'onlinestore'},
260 260 :unknown_user => 'accept'
261 261 )
262 262 end
263 263 end
264 264 end
265 265
266 266 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
267 267 assert_no_difference 'User.count' do
268 268 assert_difference 'Issue.count' do
269 269 issue = submit_email(
270 270 'ticket_by_unknown_user.eml',
271 271 :issue => {:project => 'onlinestore'},
272 272 :no_permission_check => '1',
273 273 :unknown_user => 'accept'
274 274 )
275 275 assert issue.is_a?(Issue)
276 276 assert issue.author.anonymous?
277 277 assert !issue.project.is_public?
278 278 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
279 279 end
280 280 end
281 281 end
282 282
283 283 def test_add_issue_by_created_user
284 284 Setting.default_language = 'en'
285 285 assert_difference 'User.count' do
286 286 issue = submit_email(
287 287 'ticket_by_unknown_user.eml',
288 288 :issue => {:project => 'ecookbook'},
289 289 :unknown_user => 'create'
290 290 )
291 291 assert issue.is_a?(Issue)
292 292 assert issue.author.active?
293 293 assert_equal 'john.doe@somenet.foo', issue.author.mail
294 294 assert_equal 'John', issue.author.firstname
295 295 assert_equal 'Doe', issue.author.lastname
296 296
297 297 # account information
298 298 email = ActionMailer::Base.deliveries.first
299 299 assert_not_nil email
300 300 assert email.subject.include?('account activation')
301 301 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
302 302 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
303 303 assert_equal issue.author, User.try_to_login(login, password)
304 304 end
305 305 end
306 306
307 307 def test_add_issue_without_from_header
308 308 Role.anonymous.add_permission!(:add_issues)
309 309 assert_equal false, submit_email('ticket_without_from_header.eml')
310 310 end
311 311
312 312 def test_add_issue_with_invalid_attributes
313 313 issue = submit_email(
314 314 'ticket_with_invalid_attributes.eml',
315 315 :allow_override => 'tracker,category,priority'
316 316 )
317 317 assert issue.is_a?(Issue)
318 318 assert !issue.new_record?
319 319 issue.reload
320 320 assert_nil issue.assigned_to
321 321 assert_nil issue.start_date
322 322 assert_nil issue.due_date
323 323 assert_equal 0, issue.done_ratio
324 324 assert_equal 'Normal', issue.priority.to_s
325 325 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
326 326 end
327 327
328 328 def test_add_issue_with_localized_attributes
329 329 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
330 330 issue = submit_email(
331 331 'ticket_with_localized_attributes.eml',
332 332 :allow_override => 'tracker,category,priority'
333 333 )
334 334 assert issue.is_a?(Issue)
335 335 assert !issue.new_record?
336 336 issue.reload
337 337 assert_equal 'New ticket on a given project', issue.subject
338 338 assert_equal User.find_by_login('jsmith'), issue.author
339 339 assert_equal Project.find(2), issue.project
340 340 assert_equal 'Feature request', issue.tracker.to_s
341 341 assert_equal 'Stock management', issue.category.to_s
342 342 assert_equal 'Urgent', issue.priority.to_s
343 343 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
344 344 end
345 345
346 346 def test_add_issue_with_japanese_keywords
347 347 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
348 348 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
349 349 tracker = Tracker.create!(:name => ja_dev)
350 350 Project.find(1).trackers << tracker
351 351 issue = submit_email(
352 352 'japanese_keywords_iso_2022_jp.eml',
353 353 :issue => {:project => 'ecookbook'},
354 354 :allow_override => 'tracker'
355 355 )
356 356 assert_kind_of Issue, issue
357 357 assert_equal tracker, issue.tracker
358 358 end
359 359
360 360 def test_add_issue_from_apple_mail
361 361 issue = submit_email(
362 362 'apple_mail_with_attachment.eml',
363 363 :issue => {:project => 'ecookbook'}
364 364 )
365 365 assert_kind_of Issue, issue
366 366 assert_equal 1, issue.attachments.size
367 367
368 368 attachment = issue.attachments.first
369 369 assert_equal 'paella.jpg', attachment.filename
370 370 assert_equal 10790, attachment.filesize
371 371 assert File.exist?(attachment.diskfile)
372 372 assert_equal 10790, File.size(attachment.diskfile)
373 373 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
374 374 end
375 375
376 376 def test_thunderbird_with_attachment_ja
377 377 issue = submit_email(
378 378 'thunderbird_with_attachment_ja.eml',
379 379 :issue => {:project => 'ecookbook'}
380 380 )
381 381 assert_kind_of Issue, issue
382 382 assert_equal 1, issue.attachments.size
383 383 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
384 384 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
385 385 attachment = issue.attachments.first
386 386 assert_equal ja, attachment.filename
387 387 assert_equal 5, attachment.filesize
388 388 assert File.exist?(attachment.diskfile)
389 389 assert_equal 5, File.size(attachment.diskfile)
390 390 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
391 391 end
392 392
393 393 def test_gmail_with_attachment_ja
394 394 issue = submit_email(
395 395 'gmail_with_attachment_ja.eml',
396 396 :issue => {:project => 'ecookbook'}
397 397 )
398 398 assert_kind_of Issue, issue
399 399 assert_equal 1, issue.attachments.size
400 400 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt"
401 401 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
402 402 attachment = issue.attachments.first
403 403 assert_equal ja, attachment.filename
404 404 assert_equal 5, attachment.filesize
405 405 assert File.exist?(attachment.diskfile)
406 406 assert_equal 5, File.size(attachment.diskfile)
407 407 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
408 408 end
409 409
410 410 def test_thunderbird_with_attachment_latin1
411 411 issue = submit_email(
412 412 'thunderbird_with_attachment_iso-8859-1.eml',
413 413 :issue => {:project => 'ecookbook'}
414 414 )
415 415 assert_kind_of Issue, issue
416 416 assert_equal 1, issue.attachments.size
417 417 u = ""
418 418 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
419 419 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
420 420 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
421 421 11.times { u << u1 }
422 422 attachment = issue.attachments.first
423 423 assert_equal "#{u}.png", attachment.filename
424 424 assert_equal 130, attachment.filesize
425 425 assert File.exist?(attachment.diskfile)
426 426 assert_equal 130, File.size(attachment.diskfile)
427 427 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
428 428 end
429 429
430 430 def test_gmail_with_attachment_latin1
431 431 issue = submit_email(
432 432 'gmail_with_attachment_iso-8859-1.eml',
433 433 :issue => {:project => 'ecookbook'}
434 434 )
435 435 assert_kind_of Issue, issue
436 436 assert_equal 1, issue.attachments.size
437 437 u = ""
438 438 u.force_encoding('UTF-8') if u.respond_to?(:force_encoding)
439 439 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc"
440 440 u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding)
441 441 11.times { u << u1 }
442 442 attachment = issue.attachments.first
443 443 assert_equal "#{u}.txt", attachment.filename
444 444 assert_equal 5, attachment.filesize
445 445 assert File.exist?(attachment.diskfile)
446 446 assert_equal 5, File.size(attachment.diskfile)
447 447 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
448 448 end
449 449
450 450 def test_add_issue_with_iso_8859_1_subject
451 451 issue = submit_email(
452 452 'subject_as_iso-8859-1.eml',
453 453 :issue => {:project => 'ecookbook'}
454 454 )
455 455 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..."
456 456 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
457 457 assert_kind_of Issue, issue
458 458 assert_equal str, issue.subject
459 459 end
460 460
461 461 def test_add_issue_with_japanese_subject
462 462 issue = submit_email(
463 463 'subject_japanese_1.eml',
464 464 :issue => {:project => 'ecookbook'}
465 465 )
466 466 assert_kind_of Issue, issue
467 467 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
468 468 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
469 469 assert_equal ja, issue.subject
470 470 end
471 471
472 472 def test_add_issue_with_no_subject_header
473 473 issue = submit_email(
474 474 'no_subject_header.eml',
475 475 :issue => {:project => 'ecookbook'}
476 476 )
477 477 assert_kind_of Issue, issue
478 478 assert_equal '(no subject)', issue.subject
479 479 end
480 480
481 481 def test_add_issue_with_mixed_japanese_subject
482 482 issue = submit_email(
483 483 'subject_japanese_2.eml',
484 484 :issue => {:project => 'ecookbook'}
485 485 )
486 486 assert_kind_of Issue, issue
487 487 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"
488 488 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
489 489 assert_equal ja, issue.subject
490 490 end
491 491
492 492 def test_should_ignore_emails_from_locked_users
493 493 User.find(2).lock!
494 494
495 495 MailHandler.any_instance.expects(:dispatch).never
496 496 assert_no_difference 'Issue.count' do
497 497 assert_equal false, submit_email('ticket_on_given_project.eml')
498 498 end
499 499 end
500 500
501 501 def test_should_ignore_emails_from_emission_address
502 502 Role.anonymous.add_permission!(:add_issues)
503 503 assert_no_difference 'User.count' do
504 504 assert_equal false,
505 505 submit_email(
506 506 'ticket_from_emission_address.eml',
507 507 :issue => {:project => 'ecookbook'},
508 508 :unknown_user => 'create'
509 509 )
510 510 end
511 511 end
512 512
513 513 def test_should_ignore_auto_replied_emails
514 514 MailHandler.any_instance.expects(:dispatch).never
515 515 [
516 516 "X-Auto-Response-Suppress: OOF",
517 517 "Auto-Submitted: auto-replied",
518 518 "Auto-Submitted: Auto-Replied",
519 519 "Auto-Submitted: auto-generated"
520 520 ].each do |header|
521 521 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
522 522 raw = header + "\n" + raw
523 523
524 524 assert_no_difference 'Issue.count' do
525 525 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
526 526 end
527 527 end
528 528 end
529 529
530 530 def test_add_issue_should_send_email_notification
531 531 Setting.notified_events = ['issue_added']
532 532 ActionMailer::Base.deliveries.clear
533 533 # This email contains: 'Project: onlinestore'
534 534 issue = submit_email('ticket_on_given_project.eml')
535 535 assert issue.is_a?(Issue)
536 536 assert_equal 1, ActionMailer::Base.deliveries.size
537 537 end
538 538
539 539 def test_update_issue
540 540 journal = submit_email('ticket_reply.eml')
541 541 assert journal.is_a?(Journal)
542 542 assert_equal User.find_by_login('jsmith'), journal.user
543 543 assert_equal Issue.find(2), journal.journalized
544 544 assert_match /This is reply/, journal.notes
545 545 assert_equal false, journal.private_notes
546 546 assert_equal 'Feature request', journal.issue.tracker.name
547 547 end
548 548
549 549 def test_update_issue_with_attribute_changes
550 550 # This email contains: 'Status: Resolved'
551 551 journal = submit_email('ticket_reply_with_status.eml')
552 552 assert journal.is_a?(Journal)
553 553 issue = Issue.find(journal.issue.id)
554 554 assert_equal User.find_by_login('jsmith'), journal.user
555 555 assert_equal Issue.find(2), journal.journalized
556 556 assert_match /This is reply/, journal.notes
557 557 assert_equal 'Feature request', journal.issue.tracker.name
558 558 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
559 559 assert_equal '2010-01-01', issue.start_date.to_s
560 560 assert_equal '2010-12-31', issue.due_date.to_s
561 561 assert_equal User.find_by_login('jsmith'), issue.assigned_to
562 562 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
563 563 # keywords should be removed from the email body
564 564 assert !journal.notes.match(/^Status:/i)
565 565 assert !journal.notes.match(/^Start Date:/i)
566 566 end
567 567
568 568 def test_update_issue_with_attachment
569 569 assert_difference 'Journal.count' do
570 570 assert_difference 'JournalDetail.count' do
571 571 assert_difference 'Attachment.count' do
572 572 assert_no_difference 'Issue.count' do
573 573 journal = submit_email('ticket_with_attachment.eml') do |raw|
574 574 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
575 575 end
576 576 end
577 577 end
578 578 end
579 579 end
580 580 journal = Journal.first(:order => 'id DESC')
581 581 assert_equal Issue.find(2), journal.journalized
582 582 assert_equal 1, journal.details.size
583 583
584 584 detail = journal.details.first
585 585 assert_equal 'attachment', detail.property
586 586 assert_equal 'Paella.jpg', detail.value
587 587 end
588 588
589 589 def test_update_issue_should_send_email_notification
590 590 ActionMailer::Base.deliveries.clear
591 591 journal = submit_email('ticket_reply.eml')
592 592 assert journal.is_a?(Journal)
593 593 assert_equal 1, ActionMailer::Base.deliveries.size
594 594 end
595 595
596 596 def test_update_issue_should_not_set_defaults
597 597 journal = submit_email(
598 598 'ticket_reply.eml',
599 599 :issue => {:tracker => 'Support request', :priority => 'High'}
600 600 )
601 601 assert journal.is_a?(Journal)
602 602 assert_match /This is reply/, journal.notes
603 603 assert_equal 'Feature request', journal.issue.tracker.name
604 604 assert_equal 'Normal', journal.issue.priority.name
605 605 end
606 606
607 607 def test_replying_to_a_private_note_should_add_reply_as_private
608 608 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
609 609
610 610 assert_difference 'Journal.count' do
611 611 journal = submit_email('ticket_reply.eml') do |email|
612 612 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
613 613 end
614 614
615 615 assert_kind_of Journal, journal
616 616 assert_match /This is reply/, journal.notes
617 617 assert_equal true, journal.private_notes
618 618 end
619 619 end
620 620
621 621 def test_reply_to_a_message
622 622 m = submit_email('message_reply.eml')
623 623 assert m.is_a?(Message)
624 624 assert !m.new_record?
625 625 m.reload
626 626 assert_equal 'Reply via email', m.subject
627 627 # The email replies to message #2 which is part of the thread of message #1
628 628 assert_equal Message.find(1), m.parent
629 629 end
630 630
631 631 def test_reply_to_a_message_by_subject
632 632 m = submit_email('message_reply_by_subject.eml')
633 633 assert m.is_a?(Message)
634 634 assert !m.new_record?
635 635 m.reload
636 636 assert_equal 'Reply to the first post', m.subject
637 637 assert_equal Message.find(1), m.parent
638 638 end
639 639
640 640 def test_should_strip_tags_of_html_only_emails
641 641 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
642 642 assert issue.is_a?(Issue)
643 643 assert !issue.new_record?
644 644 issue.reload
645 645 assert_equal 'HTML email', issue.subject
646 646 assert_equal 'This is a html-only email.', issue.description
647 647 end
648 648
649 649 test "truncate emails with no setting should add the entire email into the issue" do
650 650 with_settings :mail_handler_body_delimiters => '' do
651 651 issue = submit_email('ticket_on_given_project.eml')
652 652 assert_issue_created(issue)
653 653 assert issue.description.include?('---')
654 654 assert issue.description.include?('This paragraph is after the delimiter')
655 655 end
656 656 end
657 657
658 658 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
659 659 with_settings :mail_handler_body_delimiters => '---' do
660 660 issue = submit_email('ticket_on_given_project.eml')
661 661 assert_issue_created(issue)
662 662 assert issue.description.include?('This paragraph is before delimiters')
663 663 assert issue.description.include?('--- This line starts with a delimiter')
664 664 assert !issue.description.match(/^---$/)
665 665 assert !issue.description.include?('This paragraph is after the delimiter')
666 666 end
667 667 end
668 668
669 669 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
670 670 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
671 671 journal = submit_email('issue_update_with_quoted_reply_above.eml')
672 672 assert journal.is_a?(Journal)
673 673 assert journal.notes.include?('An update to the issue by the sender.')
674 674 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
675 675 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
676 676 end
677 677 end
678 678
679 679 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
680 680 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
681 681 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
682 682 assert journal.is_a?(Journal)
683 683 assert journal.notes.include?('An update to the issue by the sender.')
684 684 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
685 685 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
686 686 end
687 687 end
688 688
689 689 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
690 690 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
691 691 issue = submit_email('ticket_on_given_project.eml')
692 692 assert_issue_created(issue)
693 693 assert issue.description.include?('This paragraph is before delimiters')
694 694 assert !issue.description.include?('BREAK')
695 695 assert !issue.description.include?('This paragraph is between delimiters')
696 696 assert !issue.description.match(/^---$/)
697 697 assert !issue.description.include?('This paragraph is after the delimiter')
698 698 end
699 699 end
700 700
701 701 def test_email_with_long_subject_line
702 702 issue = submit_email('ticket_with_long_subject.eml')
703 703 assert issue.is_a?(Issue)
704 704 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]
705 705 end
706 706
707 707 def test_new_user_from_attributes_should_return_valid_user
708 708 to_test = {
709 709 # [address, name] => [login, firstname, lastname]
710 710 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
711 711 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
712 712 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
713 713 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
714 714 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
715 715 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
716 716 }
717 717
718 718 to_test.each do |attrs, expected|
719 719 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
720 720
721 721 assert user.valid?, user.errors.full_messages.to_s
722 722 assert_equal attrs.first, user.mail
723 723 assert_equal expected[0], user.login
724 724 assert_equal expected[1], user.firstname
725 725 assert_equal expected[2], user.lastname
726 726 end
727 727 end
728 728
729 def test_new_user_from_attributes_should_respect_minimum_password_length
730 with_settings :password_min_length => 15 do
731 user = MailHandler.new_user_from_attributes('jsmith@example.net')
732 assert user.valid?
733 assert user.password.length >= 15
734 end
735 end
736
737 729 def test_new_user_from_attributes_should_use_default_login_if_invalid
738 730 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
739 731 assert user.valid?
740 732 assert user.login =~ /^user[a-f0-9]+$/
741 733 assert_equal 'foo+bar@example.net', user.mail
742 734 end
743 735
744 736 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
745 737 assert_difference 'User.count' do
746 738 issue = submit_email(
747 739 'fullname_of_sender_as_utf8_encoded.eml',
748 740 :issue => {:project => 'ecookbook'},
749 741 :unknown_user => 'create'
750 742 )
751 743 end
752 744
753 745 user = User.first(:order => 'id DESC')
754 746 assert_equal "foo@example.org", user.mail
755 747 str1 = "\xc3\x84\xc3\xa4"
756 748 str2 = "\xc3\x96\xc3\xb6"
757 749 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
758 750 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
759 751 assert_equal str1, user.firstname
760 752 assert_equal str2, user.lastname
761 753 end
762 754
763 755 private
764 756
765 757 def submit_email(filename, options={})
766 758 raw = IO.read(File.join(FIXTURES_PATH, filename))
767 759 yield raw if block_given?
768 760 MailHandler.receive(raw, options)
769 761 end
770 762
771 763 def assert_issue_created(issue)
772 764 assert issue.is_a?(Issue)
773 765 assert !issue.new_record?
774 766 issue.reload
775 767 end
776 768 end
@@ -1,1105 +1,1119
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 UserTest < ActiveSupport::TestCase
21 21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources,
22 22 :trackers, :issue_statuses,
23 23 :projects_trackers,
24 24 :watchers,
25 25 :issue_categories, :enumerations, :issues,
26 26 :journals, :journal_details,
27 27 :groups_users,
28 28 :enabled_modules
29 29
30 30 def setup
31 31 @admin = User.find(1)
32 32 @jsmith = User.find(2)
33 33 @dlopper = User.find(3)
34 34 end
35 35
36 36 def test_sorted_scope_should_sort_user_by_display_name
37 37 assert_equal User.all.map(&:name).map(&:downcase).sort, User.sorted.all.map(&:name).map(&:downcase)
38 38 end
39 39
40 40 def test_generate
41 41 User.generate!(:firstname => 'Testing connection')
42 42 User.generate!(:firstname => 'Testing connection')
43 43 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
44 44 end
45 45
46 46 def test_truth
47 47 assert_kind_of User, @jsmith
48 48 end
49 49
50 50 def test_mail_should_be_stripped
51 51 u = User.new
52 52 u.mail = " foo@bar.com "
53 53 assert_equal "foo@bar.com", u.mail
54 54 end
55 55
56 56 def test_mail_validation
57 57 u = User.new
58 58 u.mail = ''
59 59 assert !u.valid?
60 60 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
61 61 end
62 62
63 63 def test_login_length_validation
64 64 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
65 65 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
66 66 assert !user.valid?
67 67
68 68 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
69 69 assert user.valid?
70 70 assert user.save
71 71 end
72 72
73 def test_generate_password_should_respect_minimum_password_length
74 with_settings :password_min_length => 15 do
75 user = User.generate!(:generate_password => true)
76 assert user.password.length >= 15
77 end
78 end
79
80 def test_generate_password_should_not_generate_password_with_less_than_10_characters
81 with_settings :password_min_length => 4 do
82 user = User.generate!(:generate_password => true)
83 assert user.password.length >= 10
84 end
85 end
86
73 87 def test_generate_password_on_create_should_set_password
74 88 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
75 89 user.login = "newuser"
76 90 user.generate_password = true
77 91 assert user.save
78 92
79 93 password = user.password
80 94 assert user.check_password?(password)
81 95 end
82 96
83 97 def test_generate_password_on_update_should_update_password
84 98 user = User.find(2)
85 99 hash = user.hashed_password
86 100 user.generate_password = true
87 101 assert user.save
88 102
89 103 password = user.password
90 104 assert user.check_password?(password)
91 105 assert_not_equal hash, user.reload.hashed_password
92 106 end
93 107
94 108 def test_create
95 109 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
96 110
97 111 user.login = "jsmith"
98 112 user.password, user.password_confirmation = "password", "password"
99 113 # login uniqueness
100 114 assert !user.save
101 115 assert_equal 1, user.errors.count
102 116
103 117 user.login = "newuser"
104 118 user.password, user.password_confirmation = "password", "pass"
105 119 # password confirmation
106 120 assert !user.save
107 121 assert_equal 1, user.errors.count
108 122
109 123 user.password, user.password_confirmation = "password", "password"
110 124 assert user.save
111 125 end
112 126
113 127 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
114 128 @user1 = User.generate!
115 129 assert_equal 'only_my_events', @user1.mail_notification
116 130 with_settings :default_notification_option => 'all' do
117 131 @user2 = User.generate!
118 132 assert_equal 'all', @user2.mail_notification
119 133 end
120 134 end
121 135
122 136 def test_user_login_should_be_case_insensitive
123 137 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
124 138 u.login = 'newuser'
125 139 u.password, u.password_confirmation = "password", "password"
126 140 assert u.save
127 141 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
128 142 u.login = 'NewUser'
129 143 u.password, u.password_confirmation = "password", "password"
130 144 assert !u.save
131 145 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
132 146 end
133 147
134 148 def test_mail_uniqueness_should_not_be_case_sensitive
135 149 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
136 150 u.login = 'newuser1'
137 151 u.password, u.password_confirmation = "password", "password"
138 152 assert u.save
139 153
140 154 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
141 155 u.login = 'newuser2'
142 156 u.password, u.password_confirmation = "password", "password"
143 157 assert !u.save
144 158 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail]
145 159 end
146 160
147 161 def test_update
148 162 assert_equal "admin", @admin.login
149 163 @admin.login = "john"
150 164 assert @admin.save, @admin.errors.full_messages.join("; ")
151 165 @admin.reload
152 166 assert_equal "john", @admin.login
153 167 end
154 168
155 169 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
156 170 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
157 171 u1.login = 'newuser1'
158 172 assert u1.save
159 173
160 174 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
161 175 u2.login = 'newuser1'
162 176 assert u2.save(:validate => false)
163 177
164 178 user = User.find(u2.id)
165 179 user.firstname = "firstname"
166 180 assert user.save, "Save failed"
167 181 end
168 182
169 183 def test_destroy_should_delete_members_and_roles
170 184 members = Member.find_all_by_user_id(2)
171 185 ms = members.size
172 186 rs = members.collect(&:roles).flatten.size
173 187
174 188 assert_difference 'Member.count', - ms do
175 189 assert_difference 'MemberRole.count', - rs do
176 190 User.find(2).destroy
177 191 end
178 192 end
179 193
180 194 assert_nil User.find_by_id(2)
181 195 assert Member.find_all_by_user_id(2).empty?
182 196 end
183 197
184 198 def test_destroy_should_update_attachments
185 199 attachment = Attachment.create!(:container => Project.find(1),
186 200 :file => uploaded_test_file("testfile.txt", "text/plain"),
187 201 :author_id => 2)
188 202
189 203 User.find(2).destroy
190 204 assert_nil User.find_by_id(2)
191 205 assert_equal User.anonymous, attachment.reload.author
192 206 end
193 207
194 208 def test_destroy_should_update_comments
195 209 comment = Comment.create!(
196 210 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
197 211 :author => User.find(2),
198 212 :comments => 'foo'
199 213 )
200 214
201 215 User.find(2).destroy
202 216 assert_nil User.find_by_id(2)
203 217 assert_equal User.anonymous, comment.reload.author
204 218 end
205 219
206 220 def test_destroy_should_update_issues
207 221 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
208 222
209 223 User.find(2).destroy
210 224 assert_nil User.find_by_id(2)
211 225 assert_equal User.anonymous, issue.reload.author
212 226 end
213 227
214 228 def test_destroy_should_unassign_issues
215 229 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
216 230
217 231 User.find(2).destroy
218 232 assert_nil User.find_by_id(2)
219 233 assert_nil issue.reload.assigned_to
220 234 end
221 235
222 236 def test_destroy_should_update_journals
223 237 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
224 238 issue.init_journal(User.find(2), "update")
225 239 issue.save!
226 240
227 241 User.find(2).destroy
228 242 assert_nil User.find_by_id(2)
229 243 assert_equal User.anonymous, issue.journals.first.reload.user
230 244 end
231 245
232 246 def test_destroy_should_update_journal_details_old_value
233 247 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
234 248 issue.init_journal(User.find(1), "update")
235 249 issue.assigned_to_id = nil
236 250 assert_difference 'JournalDetail.count' do
237 251 issue.save!
238 252 end
239 253 journal_detail = JournalDetail.first(:order => 'id DESC')
240 254 assert_equal '2', journal_detail.old_value
241 255
242 256 User.find(2).destroy
243 257 assert_nil User.find_by_id(2)
244 258 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
245 259 end
246 260
247 261 def test_destroy_should_update_journal_details_value
248 262 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
249 263 issue.init_journal(User.find(1), "update")
250 264 issue.assigned_to_id = 2
251 265 assert_difference 'JournalDetail.count' do
252 266 issue.save!
253 267 end
254 268 journal_detail = JournalDetail.first(:order => 'id DESC')
255 269 assert_equal '2', journal_detail.value
256 270
257 271 User.find(2).destroy
258 272 assert_nil User.find_by_id(2)
259 273 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
260 274 end
261 275
262 276 def test_destroy_should_update_messages
263 277 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
264 278 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
265 279
266 280 User.find(2).destroy
267 281 assert_nil User.find_by_id(2)
268 282 assert_equal User.anonymous, message.reload.author
269 283 end
270 284
271 285 def test_destroy_should_update_news
272 286 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
273 287
274 288 User.find(2).destroy
275 289 assert_nil User.find_by_id(2)
276 290 assert_equal User.anonymous, news.reload.author
277 291 end
278 292
279 293 def test_destroy_should_delete_private_queries
280 294 query = Query.new(:name => 'foo', :is_public => false)
281 295 query.project_id = 1
282 296 query.user_id = 2
283 297 query.save!
284 298
285 299 User.find(2).destroy
286 300 assert_nil User.find_by_id(2)
287 301 assert_nil Query.find_by_id(query.id)
288 302 end
289 303
290 304 def test_destroy_should_update_public_queries
291 305 query = Query.new(:name => 'foo', :is_public => true)
292 306 query.project_id = 1
293 307 query.user_id = 2
294 308 query.save!
295 309
296 310 User.find(2).destroy
297 311 assert_nil User.find_by_id(2)
298 312 assert_equal User.anonymous, query.reload.user
299 313 end
300 314
301 315 def test_destroy_should_update_time_entries
302 316 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
303 317 entry.project_id = 1
304 318 entry.user_id = 2
305 319 entry.save!
306 320
307 321 User.find(2).destroy
308 322 assert_nil User.find_by_id(2)
309 323 assert_equal User.anonymous, entry.reload.user
310 324 end
311 325
312 326 def test_destroy_should_delete_tokens
313 327 token = Token.create!(:user_id => 2, :value => 'foo')
314 328
315 329 User.find(2).destroy
316 330 assert_nil User.find_by_id(2)
317 331 assert_nil Token.find_by_id(token.id)
318 332 end
319 333
320 334 def test_destroy_should_delete_watchers
321 335 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
322 336 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
323 337
324 338 User.find(2).destroy
325 339 assert_nil User.find_by_id(2)
326 340 assert_nil Watcher.find_by_id(watcher.id)
327 341 end
328 342
329 343 def test_destroy_should_update_wiki_contents
330 344 wiki_content = WikiContent.create!(
331 345 :text => 'foo',
332 346 :author_id => 2,
333 347 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
334 348 )
335 349 wiki_content.text = 'bar'
336 350 assert_difference 'WikiContent::Version.count' do
337 351 wiki_content.save!
338 352 end
339 353
340 354 User.find(2).destroy
341 355 assert_nil User.find_by_id(2)
342 356 assert_equal User.anonymous, wiki_content.reload.author
343 357 wiki_content.versions.each do |version|
344 358 assert_equal User.anonymous, version.reload.author
345 359 end
346 360 end
347 361
348 362 def test_destroy_should_nullify_issue_categories
349 363 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
350 364
351 365 User.find(2).destroy
352 366 assert_nil User.find_by_id(2)
353 367 assert_nil category.reload.assigned_to_id
354 368 end
355 369
356 370 def test_destroy_should_nullify_changesets
357 371 changeset = Changeset.create!(
358 372 :repository => Repository::Subversion.create!(
359 373 :project_id => 1,
360 374 :url => 'file:///tmp',
361 375 :identifier => 'tmp'
362 376 ),
363 377 :revision => '12',
364 378 :committed_on => Time.now,
365 379 :committer => 'jsmith'
366 380 )
367 381 assert_equal 2, changeset.user_id
368 382
369 383 User.find(2).destroy
370 384 assert_nil User.find_by_id(2)
371 385 assert_nil changeset.reload.user_id
372 386 end
373 387
374 388 def test_anonymous_user_should_not_be_destroyable
375 389 assert_no_difference 'User.count' do
376 390 assert_equal false, User.anonymous.destroy
377 391 end
378 392 end
379 393
380 394 def test_validate_login_presence
381 395 @admin.login = ""
382 396 assert !@admin.save
383 397 assert_equal 1, @admin.errors.count
384 398 end
385 399
386 400 def test_validate_mail_notification_inclusion
387 401 u = User.new
388 402 u.mail_notification = 'foo'
389 403 u.save
390 404 assert_not_nil u.errors[:mail_notification]
391 405 end
392 406
393 407 context "User#try_to_login" do
394 408 should "fall-back to case-insensitive if user login is not found as-typed." do
395 409 user = User.try_to_login("AdMin", "admin")
396 410 assert_kind_of User, user
397 411 assert_equal "admin", user.login
398 412 end
399 413
400 414 should "select the exact matching user first" do
401 415 case_sensitive_user = User.generate! do |user|
402 416 user.password = "admin123"
403 417 end
404 418 # bypass validations to make it appear like existing data
405 419 case_sensitive_user.update_attribute(:login, 'ADMIN')
406 420
407 421 user = User.try_to_login("ADMIN", "admin123")
408 422 assert_kind_of User, user
409 423 assert_equal "ADMIN", user.login
410 424
411 425 end
412 426 end
413 427
414 428 def test_password
415 429 user = User.try_to_login("admin", "admin")
416 430 assert_kind_of User, user
417 431 assert_equal "admin", user.login
418 432 user.password = "hello123"
419 433 assert user.save
420 434
421 435 user = User.try_to_login("admin", "hello123")
422 436 assert_kind_of User, user
423 437 assert_equal "admin", user.login
424 438 end
425 439
426 440 def test_validate_password_length
427 441 with_settings :password_min_length => '100' do
428 442 user = User.new(:firstname => "new100", :lastname => "user100", :mail => "newuser100@somenet.foo")
429 443 user.login = "newuser100"
430 444 user.password, user.password_confirmation = "password100", "password100"
431 445 assert !user.save
432 446 assert_equal 1, user.errors.count
433 447 end
434 448 end
435 449
436 450 def test_name_format
437 451 assert_equal 'John S.', @jsmith.name(:firstname_lastinitial)
438 452 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
439 453 with_settings :user_format => :firstname_lastname do
440 454 assert_equal 'John Smith', @jsmith.reload.name
441 455 end
442 456 with_settings :user_format => :username do
443 457 assert_equal 'jsmith', @jsmith.reload.name
444 458 end
445 459 with_settings :user_format => :lastname do
446 460 assert_equal 'Smith', @jsmith.reload.name
447 461 end
448 462 end
449 463
450 464 def test_today_should_return_the_day_according_to_user_time_zone
451 465 preference = User.find(1).pref
452 466 date = Date.new(2012, 05, 15)
453 467 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
454 468 Date.stubs(:today).returns(date)
455 469 Time.stubs(:now).returns(time)
456 470
457 471 preference.update_attribute :time_zone, 'Baku' # UTC+4
458 472 assert_equal '2012-05-16', User.find(1).today.to_s
459 473
460 474 preference.update_attribute :time_zone, 'La Paz' # UTC-4
461 475 assert_equal '2012-05-15', User.find(1).today.to_s
462 476
463 477 preference.update_attribute :time_zone, ''
464 478 assert_equal '2012-05-15', User.find(1).today.to_s
465 479 end
466 480
467 481 def test_time_to_date_should_return_the_date_according_to_user_time_zone
468 482 preference = User.find(1).pref
469 483 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
470 484
471 485 preference.update_attribute :time_zone, 'Baku' # UTC+4
472 486 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
473 487
474 488 preference.update_attribute :time_zone, 'La Paz' # UTC-4
475 489 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
476 490
477 491 preference.update_attribute :time_zone, ''
478 492 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
479 493 end
480 494
481 495 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
482 496 with_settings :user_format => 'lastname_coma_firstname' do
483 497 assert_equal ['users.lastname', 'users.firstname', 'users.id'], User.fields_for_order_statement
484 498 end
485 499 end
486 500
487 501 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
488 502 with_settings :user_format => 'lastname_firstname' do
489 503 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'], User.fields_for_order_statement('authors')
490 504 end
491 505 end
492 506
493 507 def test_fields_for_order_statement_with_blank_format_should_return_default
494 508 with_settings :user_format => '' do
495 509 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
496 510 end
497 511 end
498 512
499 513 def test_fields_for_order_statement_with_invalid_format_should_return_default
500 514 with_settings :user_format => 'foo' do
501 515 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
502 516 end
503 517 end
504 518
505 519 def test_lock
506 520 user = User.try_to_login("jsmith", "jsmith")
507 521 assert_equal @jsmith, user
508 522
509 523 @jsmith.status = User::STATUS_LOCKED
510 524 assert @jsmith.save
511 525
512 526 user = User.try_to_login("jsmith", "jsmith")
513 527 assert_equal nil, user
514 528 end
515 529
516 530 context ".try_to_login" do
517 531 context "with good credentials" do
518 532 should "return the user" do
519 533 user = User.try_to_login("admin", "admin")
520 534 assert_kind_of User, user
521 535 assert_equal "admin", user.login
522 536 end
523 537 end
524 538
525 539 context "with wrong credentials" do
526 540 should "return nil" do
527 541 assert_nil User.try_to_login("admin", "foo")
528 542 end
529 543 end
530 544 end
531 545
532 546 if ldap_configured?
533 547 context "#try_to_login using LDAP" do
534 548 context "with failed connection to the LDAP server" do
535 549 should "return nil" do
536 550 @auth_source = AuthSourceLdap.find(1)
537 551 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
538 552
539 553 assert_equal nil, User.try_to_login('edavis', 'wrong')
540 554 end
541 555 end
542 556
543 557 context "with an unsuccessful authentication" do
544 558 should "return nil" do
545 559 assert_equal nil, User.try_to_login('edavis', 'wrong')
546 560 end
547 561 end
548 562
549 563 context "binding with user's account" do
550 564 setup do
551 565 @auth_source = AuthSourceLdap.find(1)
552 566 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
553 567 @auth_source.account_password = ''
554 568 @auth_source.save!
555 569
556 570 @ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
557 571 @ldap_user.login = 'example1'
558 572 @ldap_user.save!
559 573 end
560 574
561 575 context "with a successful authentication" do
562 576 should "return the user" do
563 577 assert_equal @ldap_user, User.try_to_login('example1', '123456')
564 578 end
565 579 end
566 580
567 581 context "with an unsuccessful authentication" do
568 582 should "return nil" do
569 583 assert_nil User.try_to_login('example1', '11111')
570 584 end
571 585 end
572 586 end
573 587
574 588 context "on the fly registration" do
575 589 setup do
576 590 @auth_source = AuthSourceLdap.find(1)
577 591 @auth_source.update_attribute :onthefly_register, true
578 592 end
579 593
580 594 context "with a successful authentication" do
581 595 should "create a new user account if it doesn't exist" do
582 596 assert_difference('User.count') do
583 597 user = User.try_to_login('edavis', '123456')
584 598 assert !user.admin?
585 599 end
586 600 end
587 601
588 602 should "retrieve existing user" do
589 603 user = User.try_to_login('edavis', '123456')
590 604 user.admin = true
591 605 user.save!
592 606
593 607 assert_no_difference('User.count') do
594 608 user = User.try_to_login('edavis', '123456')
595 609 assert user.admin?
596 610 end
597 611 end
598 612 end
599 613
600 614 context "binding with user's account" do
601 615 setup do
602 616 @auth_source = AuthSourceLdap.find(1)
603 617 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
604 618 @auth_source.account_password = ''
605 619 @auth_source.save!
606 620 end
607 621
608 622 context "with a successful authentication" do
609 623 should "create a new user account if it doesn't exist" do
610 624 assert_difference('User.count') do
611 625 user = User.try_to_login('example1', '123456')
612 626 assert_kind_of User, user
613 627 end
614 628 end
615 629 end
616 630
617 631 context "with an unsuccessful authentication" do
618 632 should "return nil" do
619 633 assert_nil User.try_to_login('example1', '11111')
620 634 end
621 635 end
622 636 end
623 637 end
624 638 end
625 639
626 640 else
627 641 puts "Skipping LDAP tests."
628 642 end
629 643
630 644 def test_create_anonymous
631 645 AnonymousUser.delete_all
632 646 anon = User.anonymous
633 647 assert !anon.new_record?
634 648 assert_kind_of AnonymousUser, anon
635 649 end
636 650
637 651 def test_ensure_single_anonymous_user
638 652 AnonymousUser.delete_all
639 653 anon1 = User.anonymous
640 654 assert !anon1.new_record?
641 655 assert_kind_of AnonymousUser, anon1
642 656 anon2 = AnonymousUser.create(
643 657 :lastname => 'Anonymous', :firstname => '',
644 658 :mail => '', :login => '', :status => 0)
645 659 assert_equal 1, anon2.errors.count
646 660 end
647 661
648 662 def test_rss_key
649 663 assert_nil @jsmith.rss_token
650 664 key = @jsmith.rss_key
651 665 assert_equal 40, key.length
652 666
653 667 @jsmith.reload
654 668 assert_equal key, @jsmith.rss_key
655 669 end
656 670
657 671 def test_rss_key_should_not_be_generated_twice
658 672 assert_difference 'Token.count', 1 do
659 673 key1 = @jsmith.rss_key
660 674 key2 = @jsmith.rss_key
661 675 assert_equal key1, key2
662 676 end
663 677 end
664 678
665 679 def test_api_key_should_not_be_generated_twice
666 680 assert_difference 'Token.count', 1 do
667 681 key1 = @jsmith.api_key
668 682 key2 = @jsmith.api_key
669 683 assert_equal key1, key2
670 684 end
671 685 end
672 686
673 687 context "User#api_key" do
674 688 should "generate a new one if the user doesn't have one" do
675 689 user = User.generate!(:api_token => nil)
676 690 assert_nil user.api_token
677 691
678 692 key = user.api_key
679 693 assert_equal 40, key.length
680 694 user.reload
681 695 assert_equal key, user.api_key
682 696 end
683 697
684 698 should "return the existing api token value" do
685 699 user = User.generate!
686 700 token = Token.create!(:action => 'api')
687 701 user.api_token = token
688 702 assert user.save
689 703
690 704 assert_equal token.value, user.api_key
691 705 end
692 706 end
693 707
694 708 context "User#find_by_api_key" do
695 709 should "return nil if no matching key is found" do
696 710 assert_nil User.find_by_api_key('zzzzzzzzz')
697 711 end
698 712
699 713 should "return nil if the key is found for an inactive user" do
700 714 user = User.generate!
701 715 user.status = User::STATUS_LOCKED
702 716 token = Token.create!(:action => 'api')
703 717 user.api_token = token
704 718 user.save
705 719
706 720 assert_nil User.find_by_api_key(token.value)
707 721 end
708 722
709 723 should "return the user if the key is found for an active user" do
710 724 user = User.generate!
711 725 token = Token.create!(:action => 'api')
712 726 user.api_token = token
713 727 user.save
714 728
715 729 assert_equal user, User.find_by_api_key(token.value)
716 730 end
717 731 end
718 732
719 733 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
720 734 user = User.find_by_login("admin")
721 735 user.password = "admin"
722 736 assert user.save(:validate => false)
723 737
724 738 assert_equal false, User.default_admin_account_changed?
725 739 end
726 740
727 741 def test_default_admin_account_changed_should_return_true_if_password_was_changed
728 742 user = User.find_by_login("admin")
729 743 user.password = "newpassword"
730 744 user.save!
731 745
732 746 assert_equal true, User.default_admin_account_changed?
733 747 end
734 748
735 749 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
736 750 user = User.find_by_login("admin")
737 751 user.password = "admin"
738 752 user.status = User::STATUS_LOCKED
739 753 assert user.save(:validate => false)
740 754
741 755 assert_equal true, User.default_admin_account_changed?
742 756 end
743 757
744 758 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
745 759 user = User.find_by_login("admin")
746 760 user.destroy
747 761
748 762 assert_equal true, User.default_admin_account_changed?
749 763 end
750 764
751 765 def test_roles_for_project
752 766 # user with a role
753 767 roles = @jsmith.roles_for_project(Project.find(1))
754 768 assert_kind_of Role, roles.first
755 769 assert_equal "Manager", roles.first.name
756 770
757 771 # user with no role
758 772 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
759 773 end
760 774
761 775 def test_projects_by_role_for_user_with_role
762 776 user = User.find(2)
763 777 assert_kind_of Hash, user.projects_by_role
764 778 assert_equal 2, user.projects_by_role.size
765 779 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
766 780 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
767 781 end
768 782
769 783 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
770 784 user = User.find(2)
771 785 assert_equal [], user.projects_by_role[Role.find(3)]
772 786 # should not update the hash
773 787 assert_nil user.projects_by_role.values.detect(&:blank?)
774 788 end
775 789
776 790 def test_projects_by_role_for_user_with_no_role
777 791 user = User.generate!
778 792 assert_equal({}, user.projects_by_role)
779 793 end
780 794
781 795 def test_projects_by_role_for_anonymous
782 796 assert_equal({}, User.anonymous.projects_by_role)
783 797 end
784 798
785 799 def test_valid_notification_options
786 800 # without memberships
787 801 assert_equal 5, User.find(7).valid_notification_options.size
788 802 # with memberships
789 803 assert_equal 6, User.find(2).valid_notification_options.size
790 804 end
791 805
792 806 def test_valid_notification_options_class_method
793 807 assert_equal 5, User.valid_notification_options.size
794 808 assert_equal 5, User.valid_notification_options(User.find(7)).size
795 809 assert_equal 6, User.valid_notification_options(User.find(2)).size
796 810 end
797 811
798 812 def test_mail_notification_all
799 813 @jsmith.mail_notification = 'all'
800 814 @jsmith.notified_project_ids = []
801 815 @jsmith.save
802 816 @jsmith.reload
803 817 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
804 818 end
805 819
806 820 def test_mail_notification_selected
807 821 @jsmith.mail_notification = 'selected'
808 822 @jsmith.notified_project_ids = [1]
809 823 @jsmith.save
810 824 @jsmith.reload
811 825 assert Project.find(1).recipients.include?(@jsmith.mail)
812 826 end
813 827
814 828 def test_mail_notification_only_my_events
815 829 @jsmith.mail_notification = 'only_my_events'
816 830 @jsmith.notified_project_ids = []
817 831 @jsmith.save
818 832 @jsmith.reload
819 833 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
820 834 end
821 835
822 836 def test_comments_sorting_preference
823 837 assert !@jsmith.wants_comments_in_reverse_order?
824 838 @jsmith.pref.comments_sorting = 'asc'
825 839 assert !@jsmith.wants_comments_in_reverse_order?
826 840 @jsmith.pref.comments_sorting = 'desc'
827 841 assert @jsmith.wants_comments_in_reverse_order?
828 842 end
829 843
830 844 def test_find_by_mail_should_be_case_insensitive
831 845 u = User.find_by_mail('JSmith@somenet.foo')
832 846 assert_not_nil u
833 847 assert_equal 'jsmith@somenet.foo', u.mail
834 848 end
835 849
836 850 def test_random_password
837 851 u = User.new
838 852 u.random_password
839 853 assert !u.password.blank?
840 854 assert !u.password_confirmation.blank?
841 855 end
842 856
843 857 context "#change_password_allowed?" do
844 858 should "be allowed if no auth source is set" do
845 859 user = User.generate!
846 860 assert user.change_password_allowed?
847 861 end
848 862
849 863 should "delegate to the auth source" do
850 864 user = User.generate!
851 865
852 866 allowed_auth_source = AuthSource.generate!
853 867 def allowed_auth_source.allow_password_changes?; true; end
854 868
855 869 denied_auth_source = AuthSource.generate!
856 870 def denied_auth_source.allow_password_changes?; false; end
857 871
858 872 assert user.change_password_allowed?
859 873
860 874 user.auth_source = allowed_auth_source
861 875 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
862 876
863 877 user.auth_source = denied_auth_source
864 878 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
865 879 end
866 880 end
867 881
868 882 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
869 883 with_settings :unsubscribe => '1' do
870 884 assert_equal true, User.find(2).own_account_deletable?
871 885 end
872 886 end
873 887
874 888 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
875 889 with_settings :unsubscribe => '0' do
876 890 assert_equal false, User.find(2).own_account_deletable?
877 891 end
878 892 end
879 893
880 894 def test_own_account_deletable_should_be_false_for_a_single_admin
881 895 User.delete_all(["admin = ? AND id <> ?", true, 1])
882 896
883 897 with_settings :unsubscribe => '1' do
884 898 assert_equal false, User.find(1).own_account_deletable?
885 899 end
886 900 end
887 901
888 902 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
889 903 User.generate! do |user|
890 904 user.admin = true
891 905 end
892 906
893 907 with_settings :unsubscribe => '1' do
894 908 assert_equal true, User.find(1).own_account_deletable?
895 909 end
896 910 end
897 911
898 912 context "#allowed_to?" do
899 913 context "with a unique project" do
900 914 should "return false if project is archived" do
901 915 project = Project.find(1)
902 916 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
903 917 assert_equal false, @admin.allowed_to?(:view_issues, Project.find(1))
904 918 end
905 919
906 920 should "return false for write action if project is closed" do
907 921 project = Project.find(1)
908 922 Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
909 923 assert_equal false, @admin.allowed_to?(:edit_project, Project.find(1))
910 924 end
911 925
912 926 should "return true for read action if project is closed" do
913 927 project = Project.find(1)
914 928 Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
915 929 assert_equal true, @admin.allowed_to?(:view_project, Project.find(1))
916 930 end
917 931
918 932 should "return false if related module is disabled" do
919 933 project = Project.find(1)
920 934 project.enabled_module_names = ["issue_tracking"]
921 935 assert_equal true, @admin.allowed_to?(:add_issues, project)
922 936 assert_equal false, @admin.allowed_to?(:view_wiki_pages, project)
923 937 end
924 938
925 939 should "authorize nearly everything for admin users" do
926 940 project = Project.find(1)
927 941 assert ! @admin.member_of?(project)
928 942 %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
929 943 assert_equal true, @admin.allowed_to?(p.to_sym, project)
930 944 end
931 945 end
932 946
933 947 should "authorize normal users depending on their roles" do
934 948 project = Project.find(1)
935 949 assert_equal true, @jsmith.allowed_to?(:delete_messages, project) #Manager
936 950 assert_equal false, @dlopper.allowed_to?(:delete_messages, project) #Developper
937 951 end
938 952 end
939 953
940 954 context "with multiple projects" do
941 955 should "return false if array is empty" do
942 956 assert_equal false, @admin.allowed_to?(:view_project, [])
943 957 end
944 958
945 959 should "return true only if user has permission on all these projects" do
946 960 assert_equal true, @admin.allowed_to?(:view_project, Project.all)
947 961 assert_equal false, @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
948 962 assert_equal true, @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
949 963 assert_equal false, @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
950 964 end
951 965
952 966 should "behave correctly with arrays of 1 project" do
953 967 assert_equal false, User.anonymous.allowed_to?(:delete_issues, [Project.first])
954 968 end
955 969 end
956 970
957 971 context "with options[:global]" do
958 972 should "authorize if user has at least one role that has this permission" do
959 973 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
960 974 @anonymous = User.find(6)
961 975 assert_equal true, @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
962 976 assert_equal false, @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
963 977 assert_equal true, @dlopper2.allowed_to?(:add_issues, nil, :global => true)
964 978 assert_equal false, @anonymous.allowed_to?(:add_issues, nil, :global => true)
965 979 assert_equal true, @anonymous.allowed_to?(:view_issues, nil, :global => true)
966 980 end
967 981 end
968 982 end
969 983
970 984 context "User#notify_about?" do
971 985 context "Issues" do
972 986 setup do
973 987 @project = Project.find(1)
974 988 @author = User.generate!
975 989 @assignee = User.generate!
976 990 @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author)
977 991 end
978 992
979 993 should "be true for a user with :all" do
980 994 @author.update_attribute(:mail_notification, 'all')
981 995 assert @author.notify_about?(@issue)
982 996 end
983 997
984 998 should "be false for a user with :none" do
985 999 @author.update_attribute(:mail_notification, 'none')
986 1000 assert ! @author.notify_about?(@issue)
987 1001 end
988 1002
989 1003 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
990 1004 @user = User.generate!(:mail_notification => 'only_my_events')
991 1005 Member.create!(:user => @user, :project => @project, :role_ids => [1])
992 1006 assert ! @user.notify_about?(@issue)
993 1007 end
994 1008
995 1009 should "be true for a user with :only_my_events and is the author" do
996 1010 @author.update_attribute(:mail_notification, 'only_my_events')
997 1011 assert @author.notify_about?(@issue)
998 1012 end
999 1013
1000 1014 should "be true for a user with :only_my_events and is the assignee" do
1001 1015 @assignee.update_attribute(:mail_notification, 'only_my_events')
1002 1016 assert @assignee.notify_about?(@issue)
1003 1017 end
1004 1018
1005 1019 should "be true for a user with :only_assigned and is the assignee" do
1006 1020 @assignee.update_attribute(:mail_notification, 'only_assigned')
1007 1021 assert @assignee.notify_about?(@issue)
1008 1022 end
1009 1023
1010 1024 should "be false for a user with :only_assigned and is not the assignee" do
1011 1025 @author.update_attribute(:mail_notification, 'only_assigned')
1012 1026 assert ! @author.notify_about?(@issue)
1013 1027 end
1014 1028
1015 1029 should "be true for a user with :only_owner and is the author" do
1016 1030 @author.update_attribute(:mail_notification, 'only_owner')
1017 1031 assert @author.notify_about?(@issue)
1018 1032 end
1019 1033
1020 1034 should "be false for a user with :only_owner and is not the author" do
1021 1035 @assignee.update_attribute(:mail_notification, 'only_owner')
1022 1036 assert ! @assignee.notify_about?(@issue)
1023 1037 end
1024 1038
1025 1039 should "be true for a user with :selected and is the author" do
1026 1040 @author.update_attribute(:mail_notification, 'selected')
1027 1041 assert @author.notify_about?(@issue)
1028 1042 end
1029 1043
1030 1044 should "be true for a user with :selected and is the assignee" do
1031 1045 @assignee.update_attribute(:mail_notification, 'selected')
1032 1046 assert @assignee.notify_about?(@issue)
1033 1047 end
1034 1048
1035 1049 should "be false for a user with :selected and is not the author or assignee" do
1036 1050 @user = User.generate!(:mail_notification => 'selected')
1037 1051 Member.create!(:user => @user, :project => @project, :role_ids => [1])
1038 1052 assert ! @user.notify_about?(@issue)
1039 1053 end
1040 1054 end
1041 1055 end
1042 1056
1043 1057 def test_notify_about_news
1044 1058 user = User.generate!
1045 1059 news = News.new
1046 1060
1047 1061 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1048 1062 user.mail_notification = option
1049 1063 assert_equal (option != 'none'), user.notify_about?(news)
1050 1064 end
1051 1065 end
1052 1066
1053 1067 def test_salt_unsalted_passwords
1054 1068 # Restore a user with an unsalted password
1055 1069 user = User.find(1)
1056 1070 user.salt = nil
1057 1071 user.hashed_password = User.hash_password("unsalted")
1058 1072 user.save!
1059 1073
1060 1074 User.salt_unsalted_passwords!
1061 1075
1062 1076 user.reload
1063 1077 # Salt added
1064 1078 assert !user.salt.blank?
1065 1079 # Password still valid
1066 1080 assert user.check_password?("unsalted")
1067 1081 assert_equal user, User.try_to_login(user.login, "unsalted")
1068 1082 end
1069 1083
1070 1084 if Object.const_defined?(:OpenID)
1071 1085
1072 1086 def test_setting_identity_url
1073 1087 normalized_open_id_url = 'http://example.com/'
1074 1088 u = User.new( :identity_url => 'http://example.com/' )
1075 1089 assert_equal normalized_open_id_url, u.identity_url
1076 1090 end
1077 1091
1078 1092 def test_setting_identity_url_without_trailing_slash
1079 1093 normalized_open_id_url = 'http://example.com/'
1080 1094 u = User.new( :identity_url => 'http://example.com' )
1081 1095 assert_equal normalized_open_id_url, u.identity_url
1082 1096 end
1083 1097
1084 1098 def test_setting_identity_url_without_protocol
1085 1099 normalized_open_id_url = 'http://example.com/'
1086 1100 u = User.new( :identity_url => 'example.com' )
1087 1101 assert_equal normalized_open_id_url, u.identity_url
1088 1102 end
1089 1103
1090 1104 def test_setting_blank_identity_url
1091 1105 u = User.new( :identity_url => 'example.com' )
1092 1106 u.identity_url = ''
1093 1107 assert u.identity_url.blank?
1094 1108 end
1095 1109
1096 1110 def test_setting_invalid_identity_url
1097 1111 u = User.new( :identity_url => 'this is not an openid url' )
1098 1112 assert u.identity_url.blank?
1099 1113 end
1100 1114
1101 1115 else
1102 1116 puts "Skipping openid tests."
1103 1117 end
1104 1118
1105 1119 end
General Comments 0
You need to be logged in to leave comments. Login now