You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mail_handler_test.rb 40KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. # encoding: utf-8
  2. #
  3. # Redmine - project management software
  4. # Copyright (C) 2006-2015 Jean-Philippe Lang
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License
  8. # as published by the Free Software Foundation; either version 2
  9. # of the License, or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. require File.expand_path('../../test_helper', __FILE__)
  20. class MailHandlerTest < ActiveSupport::TestCase
  21. fixtures :users, :projects, :enabled_modules, :roles,
  22. :members, :member_roles, :users,
  23. :email_addresses,
  24. :issues, :issue_statuses,
  25. :workflows, :trackers, :projects_trackers,
  26. :versions, :enumerations, :issue_categories,
  27. :custom_fields, :custom_fields_trackers, :custom_fields_projects,
  28. :boards, :messages
  29. FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
  30. def setup
  31. ActionMailer::Base.deliveries.clear
  32. Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
  33. end
  34. def teardown
  35. Setting.clear_cache
  36. end
  37. def test_add_issue_with_specific_overrides
  38. issue = submit_email('ticket_on_given_project.eml',
  39. :allow_override => ['status', 'start_date', 'due_date', 'assigned_to', 'fixed_version', 'estimated_hours', 'done_ratio']
  40. )
  41. assert issue.is_a?(Issue)
  42. assert !issue.new_record?
  43. issue.reload
  44. assert_equal Project.find(2), issue.project
  45. assert_equal issue.project.trackers.first, issue.tracker
  46. assert_equal 'New ticket on a given project', issue.subject
  47. assert_equal User.find_by_login('jsmith'), issue.author
  48. assert_equal IssueStatus.find_by_name('Resolved'), issue.status
  49. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  50. assert_equal '2010-01-01', issue.start_date.to_s
  51. assert_equal '2010-12-31', issue.due_date.to_s
  52. assert_equal User.find_by_login('jsmith'), issue.assigned_to
  53. assert_equal Version.find_by_name('Alpha'), issue.fixed_version
  54. assert_equal 2.5, issue.estimated_hours
  55. assert_equal 30, issue.done_ratio
  56. # keywords should be removed from the email body
  57. assert !issue.description.match(/^Project:/i)
  58. assert !issue.description.match(/^Status:/i)
  59. assert !issue.description.match(/^Start Date:/i)
  60. end
  61. def test_add_issue_with_all_overrides
  62. issue = submit_email('ticket_on_given_project.eml', :allow_override => 'all')
  63. assert issue.is_a?(Issue)
  64. assert !issue.new_record?
  65. issue.reload
  66. assert_equal Project.find(2), issue.project
  67. assert_equal issue.project.trackers.first, issue.tracker
  68. assert_equal IssueStatus.find_by_name('Resolved'), issue.status
  69. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  70. assert_equal '2010-01-01', issue.start_date.to_s
  71. assert_equal '2010-12-31', issue.due_date.to_s
  72. assert_equal User.find_by_login('jsmith'), issue.assigned_to
  73. assert_equal Version.find_by_name('Alpha'), issue.fixed_version
  74. assert_equal 2.5, issue.estimated_hours
  75. assert_equal 30, issue.done_ratio
  76. end
  77. def test_add_issue_without_overrides_should_ignore_attributes
  78. WorkflowRule.delete_all
  79. issue = submit_email('ticket_on_given_project.eml')
  80. assert issue.is_a?(Issue)
  81. assert !issue.new_record?
  82. issue.reload
  83. assert_equal Project.find(2), issue.project
  84. assert_equal 'New ticket on a given project', issue.subject
  85. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  86. assert_equal User.find_by_login('jsmith'), issue.author
  87. assert_equal issue.project.trackers.first, issue.tracker
  88. assert_equal 'New', issue.status.name
  89. assert_not_equal '2010-01-01', issue.start_date.to_s
  90. assert_nil issue.due_date
  91. assert_nil issue.assigned_to
  92. assert_nil issue.fixed_version
  93. assert_nil issue.estimated_hours
  94. assert_equal 0, issue.done_ratio
  95. end
  96. def test_add_issue_to_project_specified_by_subaddress
  97. # This email has redmine+onlinestore@somenet.foo as 'To' header
  98. issue = submit_email(
  99. 'ticket_on_project_given_by_to_header.eml',
  100. :issue => {:tracker => 'Support request'},
  101. :project_from_subaddress => 'redmine@somenet.foo'
  102. )
  103. assert issue.is_a?(Issue)
  104. assert !issue.new_record?
  105. issue.reload
  106. assert_equal 'onlinestore', issue.project.identifier
  107. assert_equal 'Support request', issue.tracker.name
  108. end
  109. def test_add_issue_with_default_tracker
  110. # This email contains: 'Project: onlinestore'
  111. issue = submit_email(
  112. 'ticket_on_given_project.eml',
  113. :issue => {:tracker => 'Support request'}
  114. )
  115. assert issue.is_a?(Issue)
  116. assert !issue.new_record?
  117. issue.reload
  118. assert_equal 'Support request', issue.tracker.name
  119. end
  120. def test_add_issue_with_default_version
  121. # This email contains: 'Project: onlinestore'
  122. issue = submit_email(
  123. 'ticket_on_given_project.eml',
  124. :issue => {:fixed_version => 'Alpha'}
  125. )
  126. assert issue.is_a?(Issue)
  127. assert !issue.new_record?
  128. assert_equal 'Alpha', issue.reload.fixed_version.name
  129. end
  130. def test_add_issue_with_status_override
  131. # This email contains: 'Project: onlinestore' and 'Status: Resolved'
  132. issue = submit_email('ticket_on_given_project.eml', :allow_override => ['status'])
  133. assert issue.is_a?(Issue)
  134. assert !issue.new_record?
  135. issue.reload
  136. assert_equal Project.find(2), issue.project
  137. assert_equal IssueStatus.find_by_name("Resolved"), issue.status
  138. end
  139. def test_add_issue_should_accept_is_private_attribute
  140. issue = submit_email('ticket_on_given_project.eml', :issue => {:is_private => '1'})
  141. assert issue.is_a?(Issue)
  142. assert !issue.new_record?
  143. assert_equal true, issue.reload.is_private
  144. end
  145. def test_add_issue_with_group_assignment
  146. with_settings :issue_group_assignment => '1' do
  147. issue = submit_email('ticket_on_given_project.eml', :allow_override => ['assigned_to']) do |email|
  148. email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
  149. end
  150. assert issue.is_a?(Issue)
  151. assert !issue.new_record?
  152. issue.reload
  153. assert_equal Group.find(11), issue.assigned_to
  154. end
  155. end
  156. def test_add_issue_with_partial_attributes_override
  157. issue = submit_email(
  158. 'ticket_with_attributes.eml',
  159. :issue => {:priority => 'High'},
  160. :allow_override => ['tracker']
  161. )
  162. assert issue.is_a?(Issue)
  163. assert !issue.new_record?
  164. issue.reload
  165. assert_equal 'New ticket on a given project', issue.subject
  166. assert_equal User.find_by_login('jsmith'), issue.author
  167. assert_equal Project.find(2), issue.project
  168. assert_equal 'Feature request', issue.tracker.to_s
  169. assert_nil issue.category
  170. assert_equal 'High', issue.priority.to_s
  171. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  172. end
  173. def test_add_issue_with_spaces_between_attribute_and_separator
  174. issue = submit_email(
  175. 'ticket_with_spaces_between_attribute_and_separator.eml',
  176. :allow_override => 'tracker,category,priority'
  177. )
  178. assert issue.is_a?(Issue)
  179. assert !issue.new_record?
  180. issue.reload
  181. assert_equal 'New ticket on a given project', issue.subject
  182. assert_equal User.find_by_login('jsmith'), issue.author
  183. assert_equal Project.find(2), issue.project
  184. assert_equal 'Feature request', issue.tracker.to_s
  185. assert_equal 'Stock management', issue.category.to_s
  186. assert_equal 'Urgent', issue.priority.to_s
  187. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  188. end
  189. def test_add_issue_with_attachment_to_specific_project
  190. issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
  191. assert issue.is_a?(Issue)
  192. assert !issue.new_record?
  193. issue.reload
  194. assert_equal 'Ticket created by email with attachment', issue.subject
  195. assert_equal User.find_by_login('jsmith'), issue.author
  196. assert_equal Project.find(2), issue.project
  197. assert_equal 'This is a new ticket with attachments', issue.description
  198. # Attachment properties
  199. assert_equal 1, issue.attachments.size
  200. assert_equal 'Paella.jpg', issue.attachments.first.filename
  201. assert_equal 'image/jpeg', issue.attachments.first.content_type
  202. assert_equal 10790, issue.attachments.first.filesize
  203. end
  204. def test_add_issue_with_custom_fields
  205. issue = submit_email('ticket_with_custom_fields.eml',
  206. :issue => {:project => 'onlinestore'}, :allow_override => ['database', 'Searchable_field']
  207. )
  208. assert issue.is_a?(Issue)
  209. assert !issue.new_record?
  210. issue.reload
  211. assert_equal 'New ticket with custom field values', issue.subject
  212. assert_equal 'PostgreSQL', issue.custom_field_value(1)
  213. assert_equal 'Value for a custom field', issue.custom_field_value(2)
  214. assert !issue.description.match(/^searchable field:/i)
  215. end
  216. def test_add_issue_with_version_custom_fields
  217. field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
  218. issue = submit_email('ticket_with_custom_fields.eml',
  219. :issue => {:project => 'ecookbook'}, :allow_override => ['affected version']
  220. ) do |email|
  221. email << "Affected version: 1.0\n"
  222. end
  223. assert issue.is_a?(Issue)
  224. assert !issue.new_record?
  225. issue.reload
  226. assert_equal '2', issue.custom_field_value(field)
  227. end
  228. def test_add_issue_should_match_assignee_on_display_name
  229. user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
  230. User.add_to_project(user, Project.find(2))
  231. issue = submit_email('ticket_on_given_project.eml', :allow_override => ['assigned_to']) do |email|
  232. email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
  233. end
  234. assert issue.is_a?(Issue)
  235. assert_equal user, issue.assigned_to
  236. end
  237. def test_add_issue_should_set_default_start_date
  238. with_settings :default_issue_start_date_to_creation_date => '1' do
  239. issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
  240. assert issue.is_a?(Issue)
  241. assert_equal Date.today, issue.start_date
  242. end
  243. end
  244. def test_add_issue_with_cc
  245. issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
  246. assert issue.is_a?(Issue)
  247. assert !issue.new_record?
  248. issue.reload
  249. assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
  250. assert_equal 1, issue.watcher_user_ids.size
  251. end
  252. def test_add_issue_from_additional_email_address
  253. user = User.find(2)
  254. user.mail = 'mainaddress@somenet.foo'
  255. user.save!
  256. EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
  257. issue = submit_email('ticket_on_given_project.eml')
  258. assert issue
  259. assert_equal user, issue.author
  260. end
  261. def test_add_issue_by_unknown_user
  262. assert_no_difference 'User.count' do
  263. assert_equal false,
  264. submit_email(
  265. 'ticket_by_unknown_user.eml',
  266. :issue => {:project => 'ecookbook'}
  267. )
  268. end
  269. end
  270. def test_add_issue_by_anonymous_user
  271. Role.anonymous.add_permission!(:add_issues)
  272. assert_no_difference 'User.count' do
  273. issue = submit_email(
  274. 'ticket_by_unknown_user.eml',
  275. :issue => {:project => 'ecookbook'},
  276. :unknown_user => 'accept'
  277. )
  278. assert issue.is_a?(Issue)
  279. assert issue.author.anonymous?
  280. end
  281. end
  282. def test_add_issue_by_anonymous_user_with_no_from_address
  283. Role.anonymous.add_permission!(:add_issues)
  284. assert_no_difference 'User.count' do
  285. issue = submit_email(
  286. 'ticket_by_empty_user.eml',
  287. :issue => {:project => 'ecookbook'},
  288. :unknown_user => 'accept'
  289. )
  290. assert issue.is_a?(Issue)
  291. assert issue.author.anonymous?
  292. end
  293. end
  294. def test_add_issue_by_anonymous_user_on_private_project
  295. Role.anonymous.add_permission!(:add_issues)
  296. assert_no_difference 'User.count' do
  297. assert_no_difference 'Issue.count' do
  298. assert_equal false,
  299. submit_email(
  300. 'ticket_by_unknown_user.eml',
  301. :issue => {:project => 'onlinestore'},
  302. :unknown_user => 'accept'
  303. )
  304. end
  305. end
  306. end
  307. def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
  308. assert_no_difference 'User.count' do
  309. assert_difference 'Issue.count' do
  310. issue = submit_email(
  311. 'ticket_by_unknown_user.eml',
  312. :issue => {:project => 'onlinestore'},
  313. :no_permission_check => '1',
  314. :unknown_user => 'accept'
  315. )
  316. assert issue.is_a?(Issue)
  317. assert issue.author.anonymous?
  318. assert !issue.project.is_public?
  319. end
  320. end
  321. end
  322. def test_add_issue_by_created_user
  323. Setting.default_language = 'en'
  324. assert_difference 'User.count' do
  325. issue = submit_email(
  326. 'ticket_by_unknown_user.eml',
  327. :issue => {:project => 'ecookbook'},
  328. :unknown_user => 'create'
  329. )
  330. assert issue.is_a?(Issue)
  331. assert issue.author.active?
  332. assert_equal 'john.doe@somenet.foo', issue.author.mail
  333. assert_equal 'John', issue.author.firstname
  334. assert_equal 'Doe', issue.author.lastname
  335. # account information
  336. email = ActionMailer::Base.deliveries.first
  337. assert_not_nil email
  338. assert email.subject.include?('account activation')
  339. login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
  340. password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
  341. assert_equal issue.author, User.try_to_login(login, password)
  342. end
  343. end
  344. def test_add_issue_should_send_notification
  345. issue = submit_email('ticket_on_given_project.eml', :allow_override => 'all')
  346. assert issue.is_a?(Issue)
  347. assert !issue.new_record?
  348. mail = ActionMailer::Base.deliveries.last
  349. assert_not_nil mail
  350. assert mail.subject.include?("##{issue.id}")
  351. assert mail.subject.include?('New ticket on a given project')
  352. end
  353. def test_created_user_should_be_added_to_groups
  354. group1 = Group.generate!
  355. group2 = Group.generate!
  356. assert_difference 'User.count' do
  357. submit_email(
  358. 'ticket_by_unknown_user.eml',
  359. :issue => {:project => 'ecookbook'},
  360. :unknown_user => 'create',
  361. :default_group => "#{group1.name},#{group2.name}"
  362. )
  363. end
  364. user = User.order('id DESC').first
  365. assert_equal [group1, group2].sort, user.groups.sort
  366. end
  367. def test_created_user_should_not_receive_account_information_with_no_account_info_option
  368. assert_difference 'User.count' do
  369. submit_email(
  370. 'ticket_by_unknown_user.eml',
  371. :issue => {:project => 'ecookbook'},
  372. :unknown_user => 'create',
  373. :no_account_notice => '1'
  374. )
  375. end
  376. # only 1 email for the new issue notification
  377. assert_equal 1, ActionMailer::Base.deliveries.size
  378. email = ActionMailer::Base.deliveries.first
  379. assert_include 'Ticket by unknown user', email.subject
  380. end
  381. def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
  382. assert_difference 'User.count' do
  383. submit_email(
  384. 'ticket_by_unknown_user.eml',
  385. :issue => {:project => 'ecookbook'},
  386. :unknown_user => 'create',
  387. :no_notification => '1'
  388. )
  389. end
  390. user = User.order('id DESC').first
  391. assert_equal 'none', user.mail_notification
  392. end
  393. def test_add_issue_without_from_header
  394. Role.anonymous.add_permission!(:add_issues)
  395. assert_equal false, submit_email('ticket_without_from_header.eml')
  396. end
  397. def test_add_issue_with_invalid_attributes
  398. with_settings :default_issue_start_date_to_creation_date => '0' do
  399. issue = submit_email(
  400. 'ticket_with_invalid_attributes.eml',
  401. :allow_override => 'tracker,category,priority'
  402. )
  403. assert issue.is_a?(Issue)
  404. assert !issue.new_record?
  405. issue.reload
  406. assert_nil issue.assigned_to
  407. assert_nil issue.start_date
  408. assert_nil issue.due_date
  409. assert_equal 0, issue.done_ratio
  410. assert_equal 'Normal', issue.priority.to_s
  411. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  412. end
  413. end
  414. def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
  415. issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
  416. email.gsub!(/^Project:.+$/, 'Project: invalid')
  417. end
  418. assert issue.is_a?(Issue)
  419. assert !issue.new_record?
  420. assert_equal 'ecookbook', issue.project.identifier
  421. end
  422. def test_add_issue_with_localized_attributes
  423. User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
  424. issue = submit_email(
  425. 'ticket_with_localized_attributes.eml',
  426. :allow_override => 'tracker,category,priority'
  427. )
  428. assert issue.is_a?(Issue)
  429. assert !issue.new_record?
  430. issue.reload
  431. assert_equal 'New ticket on a given project', issue.subject
  432. assert_equal User.find_by_login('jsmith'), issue.author
  433. assert_equal Project.find(2), issue.project
  434. assert_equal 'Feature request', issue.tracker.to_s
  435. assert_equal 'Stock management', issue.category.to_s
  436. assert_equal 'Urgent', issue.priority.to_s
  437. assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
  438. end
  439. def test_add_issue_with_japanese_keywords
  440. ja_dev = "\xe9\x96\x8b\xe7\x99\xba".force_encoding('UTF-8')
  441. tracker = Tracker.generate!(:name => ja_dev)
  442. Project.find(1).trackers << tracker
  443. issue = submit_email(
  444. 'japanese_keywords_iso_2022_jp.eml',
  445. :issue => {:project => 'ecookbook'},
  446. :allow_override => 'tracker'
  447. )
  448. assert_kind_of Issue, issue
  449. assert_equal tracker, issue.tracker
  450. end
  451. def test_add_issue_from_apple_mail
  452. issue = submit_email(
  453. 'apple_mail_with_attachment.eml',
  454. :issue => {:project => 'ecookbook'}
  455. )
  456. assert_kind_of Issue, issue
  457. assert_equal 1, issue.attachments.size
  458. attachment = issue.attachments.first
  459. assert_equal 'paella.jpg', attachment.filename
  460. assert_equal 10790, attachment.filesize
  461. assert File.exist?(attachment.diskfile)
  462. assert_equal 10790, File.size(attachment.diskfile)
  463. assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
  464. end
  465. def test_thunderbird_with_attachment_ja
  466. issue = submit_email(
  467. 'thunderbird_with_attachment_ja.eml',
  468. :issue => {:project => 'ecookbook'}
  469. )
  470. assert_kind_of Issue, issue
  471. assert_equal 1, issue.attachments.size
  472. ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
  473. attachment = issue.attachments.first
  474. assert_equal ja, attachment.filename
  475. assert_equal 5, attachment.filesize
  476. assert File.exist?(attachment.diskfile)
  477. assert_equal 5, File.size(attachment.diskfile)
  478. assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
  479. end
  480. def test_gmail_with_attachment_ja
  481. issue = submit_email(
  482. 'gmail_with_attachment_ja.eml',
  483. :issue => {:project => 'ecookbook'}
  484. )
  485. assert_kind_of Issue, issue
  486. assert_equal 1, issue.attachments.size
  487. ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
  488. attachment = issue.attachments.first
  489. assert_equal ja, attachment.filename
  490. assert_equal 5, attachment.filesize
  491. assert File.exist?(attachment.diskfile)
  492. assert_equal 5, File.size(attachment.diskfile)
  493. assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
  494. end
  495. def test_thunderbird_with_attachment_latin1
  496. issue = submit_email(
  497. 'thunderbird_with_attachment_iso-8859-1.eml',
  498. :issue => {:project => 'ecookbook'}
  499. )
  500. assert_kind_of Issue, issue
  501. assert_equal 1, issue.attachments.size
  502. u = "".force_encoding('UTF-8')
  503. u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
  504. 11.times { u << u1 }
  505. attachment = issue.attachments.first
  506. assert_equal "#{u}.png", attachment.filename
  507. assert_equal 130, attachment.filesize
  508. assert File.exist?(attachment.diskfile)
  509. assert_equal 130, File.size(attachment.diskfile)
  510. assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
  511. end
  512. def test_gmail_with_attachment_latin1
  513. issue = submit_email(
  514. 'gmail_with_attachment_iso-8859-1.eml',
  515. :issue => {:project => 'ecookbook'}
  516. )
  517. assert_kind_of Issue, issue
  518. assert_equal 1, issue.attachments.size
  519. u = "".force_encoding('UTF-8')
  520. u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
  521. 11.times { u << u1 }
  522. attachment = issue.attachments.first
  523. assert_equal "#{u}.txt", attachment.filename
  524. assert_equal 5, attachment.filesize
  525. assert File.exist?(attachment.diskfile)
  526. assert_equal 5, File.size(attachment.diskfile)
  527. assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
  528. end
  529. def test_multiple_inline_text_parts_should_be_appended_to_issue_description
  530. issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
  531. assert_include 'first', issue.description
  532. assert_include 'second', issue.description
  533. assert_include 'third', issue.description
  534. end
  535. def test_attachment_text_part_should_be_added_as_issue_attachment
  536. issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
  537. assert_not_include 'Plain text attachment', issue.description
  538. attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
  539. assert_not_nil attachment
  540. assert_include 'Plain text attachment', File.read(attachment.diskfile)
  541. end
  542. def test_add_issue_with_iso_8859_1_subject
  543. issue = submit_email(
  544. 'subject_as_iso-8859-1.eml',
  545. :issue => {:project => 'ecookbook'}
  546. )
  547. str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc...".force_encoding('UTF-8')
  548. assert_kind_of Issue, issue
  549. assert_equal str, issue.subject
  550. end
  551. def test_quoted_printable_utf8
  552. issue = submit_email(
  553. 'quoted_printable_utf8.eml',
  554. :issue => {:project => 'ecookbook'}
  555. )
  556. assert_kind_of Issue, issue
  557. str = "Freundliche Gr\xc3\xbcsse".force_encoding('UTF-8')
  558. assert_equal str, issue.description
  559. end
  560. def test_gmail_iso8859_2
  561. issue = submit_email(
  562. 'gmail-iso8859-2.eml',
  563. :issue => {:project => 'ecookbook'}
  564. )
  565. assert_kind_of Issue, issue
  566. str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87.".force_encoding('UTF-8')
  567. assert issue.description.include?(str)
  568. end
  569. def test_add_issue_with_japanese_subject
  570. issue = submit_email(
  571. 'subject_japanese_1.eml',
  572. :issue => {:project => 'ecookbook'}
  573. )
  574. assert_kind_of Issue, issue
  575. ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
  576. assert_equal ja, issue.subject
  577. end
  578. def test_add_issue_with_korean_body
  579. # Make sure mail bodies with a charset unknown to Ruby
  580. # but known to the Mail gem 2.5.4 are handled correctly
  581. kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4.".force_encoding('UTF-8')
  582. issue = submit_email(
  583. 'body_ks_c_5601-1987.eml',
  584. :issue => {:project => 'ecookbook'}
  585. )
  586. assert_kind_of Issue, issue
  587. assert_equal kr, issue.description
  588. end
  589. def test_add_issue_with_no_subject_header
  590. issue = submit_email(
  591. 'no_subject_header.eml',
  592. :issue => {:project => 'ecookbook'}
  593. )
  594. assert_kind_of Issue, issue
  595. assert_equal '(no subject)', issue.subject
  596. end
  597. def test_add_issue_with_mixed_japanese_subject
  598. issue = submit_email(
  599. 'subject_japanese_2.eml',
  600. :issue => {:project => 'ecookbook'}
  601. )
  602. assert_kind_of Issue, issue
  603. ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
  604. assert_equal ja, issue.subject
  605. end
  606. def test_should_ignore_emails_from_locked_users
  607. User.find(2).lock!
  608. MailHandler.any_instance.expects(:dispatch).never
  609. assert_no_difference 'Issue.count' do
  610. assert_equal false, submit_email('ticket_on_given_project.eml')
  611. end
  612. end
  613. def test_should_ignore_emails_from_emission_address
  614. Role.anonymous.add_permission!(:add_issues)
  615. assert_no_difference 'User.count' do
  616. assert_equal false,
  617. submit_email(
  618. 'ticket_from_emission_address.eml',
  619. :issue => {:project => 'ecookbook'},
  620. :unknown_user => 'create'
  621. )
  622. end
  623. end
  624. def test_should_ignore_auto_replied_emails
  625. MailHandler.any_instance.expects(:dispatch).never
  626. [
  627. "Auto-Submitted: auto-replied",
  628. "Auto-Submitted: Auto-Replied",
  629. "Auto-Submitted: auto-generated",
  630. 'X-Autoreply: yes'
  631. ].each do |header|
  632. raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
  633. raw = header + "\n" + raw
  634. assert_no_difference 'Issue.count' do
  635. assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
  636. end
  637. end
  638. end
  639. test "should not ignore Auto-Submitted headers not defined in RFC3834" do
  640. [
  641. "Auto-Submitted: auto-forwarded"
  642. ].each do |header|
  643. raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
  644. raw = header + "\n" + raw
  645. assert_difference 'Issue.count', 1 do
  646. assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
  647. end
  648. end
  649. end
  650. def test_add_issue_should_send_email_notification
  651. Setting.notified_events = ['issue_added']
  652. # This email contains: 'Project: onlinestore'
  653. issue = submit_email('ticket_on_given_project.eml')
  654. assert issue.is_a?(Issue)
  655. assert_equal 1, ActionMailer::Base.deliveries.size
  656. end
  657. def test_update_issue
  658. journal = submit_email('ticket_reply.eml')
  659. assert journal.is_a?(Journal)
  660. assert_equal User.find_by_login('jsmith'), journal.user
  661. assert_equal Issue.find(2), journal.journalized
  662. assert_match /This is reply/, journal.notes
  663. assert_equal false, journal.private_notes
  664. assert_equal 'Feature request', journal.issue.tracker.name
  665. end
  666. def test_update_issue_should_accept_issue_id_after_space_inside_brackets
  667. journal = submit_email('ticket_reply_with_status.eml') do |email|
  668. assert email.sub!(/^Subject:.*$/, "Subject: Re: [Feature request #2] Add ingredients categories")
  669. end
  670. assert journal.is_a?(Journal)
  671. assert_equal Issue.find(2), journal.journalized
  672. end
  673. def test_update_issue_should_accept_issue_id_inside_brackets
  674. journal = submit_email('ticket_reply_with_status.eml') do |email|
  675. assert email.sub!(/^Subject:.*$/, "Subject: Re: [#2] Add ingredients categories")
  676. end
  677. assert journal.is_a?(Journal)
  678. assert_equal Issue.find(2), journal.journalized
  679. end
  680. def test_update_issue_should_ignore_bogus_issue_ids_in_subject
  681. journal = submit_email('ticket_reply_with_status.eml') do |email|
  682. assert email.sub!(/^Subject:.*$/, "Subject: Re: [12345#1][bogus#1][Feature request #2] Add ingredients categories")
  683. end
  684. assert journal.is_a?(Journal)
  685. assert_equal Issue.find(2), journal.journalized
  686. end
  687. def test_update_issue_with_attribute_changes
  688. journal = submit_email('ticket_reply_with_status.eml', :allow_override => ['status','assigned_to','start_date','due_date', 'float field'])
  689. assert journal.is_a?(Journal)
  690. issue = Issue.find(journal.issue.id)
  691. assert_equal User.find_by_login('jsmith'), journal.user
  692. assert_equal Issue.find(2), journal.journalized
  693. assert_match /This is reply/, journal.notes
  694. assert_equal 'Feature request', journal.issue.tracker.name
  695. assert_equal IssueStatus.find_by_name("Resolved"), issue.status
  696. assert_equal '2010-01-01', issue.start_date.to_s
  697. assert_equal '2010-12-31', issue.due_date.to_s
  698. assert_equal User.find_by_login('jsmith'), issue.assigned_to
  699. assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
  700. # keywords should be removed from the email body
  701. assert !journal.notes.match(/^Status:/i)
  702. assert !journal.notes.match(/^Start Date:/i)
  703. end
  704. def test_update_issue_with_attachment
  705. assert_difference 'Journal.count' do
  706. assert_difference 'JournalDetail.count' do
  707. assert_difference 'Attachment.count' do
  708. assert_no_difference 'Issue.count' do
  709. journal = submit_email('ticket_with_attachment.eml') do |raw|
  710. raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
  711. end
  712. end
  713. end
  714. end
  715. end
  716. journal = Journal.order('id DESC').first
  717. assert_equal Issue.find(2), journal.journalized
  718. assert_equal 1, journal.details.size
  719. detail = journal.details.first
  720. assert_equal 'attachment', detail.property
  721. assert_equal 'Paella.jpg', detail.value
  722. end
  723. def test_update_issue_should_send_email_notification
  724. journal = submit_email('ticket_reply.eml')
  725. assert journal.is_a?(Journal)
  726. assert_equal 1, ActionMailer::Base.deliveries.size
  727. end
  728. def test_update_issue_should_not_set_defaults
  729. journal = submit_email(
  730. 'ticket_reply.eml',
  731. :issue => {:tracker => 'Support request', :priority => 'High'}
  732. )
  733. assert journal.is_a?(Journal)
  734. assert_match /This is reply/, journal.notes
  735. assert_equal 'Feature request', journal.issue.tracker.name
  736. assert_equal 'Normal', journal.issue.priority.name
  737. end
  738. def test_replying_to_a_private_note_should_add_reply_as_private
  739. private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
  740. assert_difference 'Journal.count' do
  741. journal = submit_email('ticket_reply.eml') do |email|
  742. email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
  743. end
  744. assert_kind_of Journal, journal
  745. assert_match /This is reply/, journal.notes
  746. assert_equal true, journal.private_notes
  747. end
  748. end
  749. def test_reply_to_a_message
  750. m = submit_email('message_reply.eml')
  751. assert m.is_a?(Message)
  752. assert !m.new_record?
  753. m.reload
  754. assert_equal 'Reply via email', m.subject
  755. # The email replies to message #2 which is part of the thread of message #1
  756. assert_equal Message.find(1), m.parent
  757. end
  758. def test_reply_to_a_message_by_subject
  759. m = submit_email('message_reply_by_subject.eml')
  760. assert m.is_a?(Message)
  761. assert !m.new_record?
  762. m.reload
  763. assert_equal 'Reply to the first post', m.subject
  764. assert_equal Message.find(1), m.parent
  765. end
  766. def test_should_convert_tags_of_html_only_emails
  767. with_settings :text_formatting => 'textile' do
  768. issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
  769. assert issue.is_a?(Issue)
  770. assert !issue.new_record?
  771. issue.reload
  772. assert_equal 'HTML email', issue.subject
  773. assert_equal "This is a *html-only* email.\r\n\r\nh1. With a title\r\n\r\nand a paragraph.", issue.description
  774. end
  775. end
  776. def test_should_handle_outlook_web_access_2010_html_only
  777. issue = submit_email('outlook_web_access_2010_html_only.eml', :issue => {:project => 'ecookbook'})
  778. assert issue.is_a?(Issue)
  779. issue.reload
  780. assert_equal 'Upgrade Redmine to 3.0.x', issue.subject
  781. assert_equal "A mess.\r\n\r\n--Geoff Maciolek\r\nMYCOMPANYNAME, LLC", issue.description
  782. end
  783. def test_should_handle_outlook_2010_html_only
  784. issue = submit_email('outlook_2010_html_only.eml', :issue => {:project => 'ecookbook'})
  785. assert issue.is_a?(Issue)
  786. issue.reload
  787. assert_equal 'Test email', issue.subject
  788. assert_equal "Simple, unadorned test email generated by Outlook 2010. It is in HTML format, but" +
  789. " no special formatting has been chosen. I’m going to save this as a draft and then manually" +
  790. " drop it into the Inbox for scraping by Redmine 3.0.2.", issue.description
  791. end
  792. test "truncate emails with no setting should add the entire email into the issue" do
  793. with_settings :mail_handler_body_delimiters => '' do
  794. issue = submit_email('ticket_on_given_project.eml')
  795. assert_issue_created(issue)
  796. assert issue.description.include?('---')
  797. assert issue.description.include?('This paragraph is after the delimiter')
  798. end
  799. end
  800. test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
  801. with_settings :mail_handler_body_delimiters => '---' do
  802. issue = submit_email('ticket_on_given_project.eml')
  803. assert_issue_created(issue)
  804. assert issue.description.include?('This paragraph is before delimiters')
  805. assert issue.description.include?('--- This line starts with a delimiter')
  806. assert !issue.description.match(/^---$/)
  807. assert !issue.description.include?('This paragraph is after the delimiter')
  808. end
  809. end
  810. test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
  811. with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
  812. journal = submit_email('issue_update_with_quoted_reply_above.eml')
  813. assert journal.is_a?(Journal)
  814. assert journal.notes.include?('An update to the issue by the sender.')
  815. assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
  816. assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
  817. end
  818. end
  819. test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
  820. with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
  821. journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
  822. assert journal.is_a?(Journal)
  823. assert journal.notes.include?('An update to the issue by the sender.')
  824. assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
  825. assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
  826. end
  827. end
  828. test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
  829. with_settings :mail_handler_body_delimiters => "---\nBREAK" do
  830. issue = submit_email('ticket_on_given_project.eml')
  831. assert_issue_created(issue)
  832. assert issue.description.include?('This paragraph is before delimiters')
  833. assert !issue.description.include?('BREAK')
  834. assert !issue.description.include?('This paragraph is between delimiters')
  835. assert !issue.description.match(/^---$/)
  836. assert !issue.description.include?('This paragraph is after the delimiter')
  837. end
  838. end
  839. def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
  840. with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
  841. issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
  842. assert issue.is_a?(Issue)
  843. assert !issue.new_record?
  844. assert_equal 0, issue.reload.attachments.size
  845. end
  846. end
  847. def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
  848. with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
  849. issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
  850. assert issue.is_a?(Issue)
  851. assert !issue.new_record?
  852. assert_equal 1, issue.reload.attachments.size
  853. end
  854. end
  855. def test_email_with_long_subject_line
  856. issue = submit_email('ticket_with_long_subject.eml')
  857. assert issue.is_a?(Issue)
  858. 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]
  859. end
  860. def test_first_keyword_should_be_matched
  861. issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
  862. assert issue.is_a?(Issue)
  863. assert_equal 'High', issue.priority.name
  864. end
  865. def test_keyword_after_delimiter_should_be_ignored
  866. with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
  867. issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
  868. assert issue.is_a?(Issue)
  869. assert_equal 'Normal', issue.priority.name
  870. end
  871. end
  872. def test_new_user_from_attributes_should_return_valid_user
  873. to_test = {
  874. # [address, name] => [login, firstname, lastname]
  875. ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
  876. ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
  877. ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
  878. ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
  879. ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
  880. ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
  881. }
  882. to_test.each do |attrs, expected|
  883. user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
  884. assert user.valid?, user.errors.full_messages.to_s
  885. assert_equal attrs.first, user.mail
  886. assert_equal expected[0], user.login
  887. assert_equal expected[1], user.firstname
  888. assert_equal expected[2], user.lastname
  889. assert_equal 'only_my_events', user.mail_notification
  890. end
  891. end
  892. def test_new_user_from_attributes_should_use_default_login_if_invalid
  893. user = MailHandler.new_user_from_attributes('foo+bar@example.net')
  894. assert user.valid?
  895. assert user.login =~ /^user[a-f0-9]+$/
  896. assert_equal 'foo+bar@example.net', user.mail
  897. end
  898. def test_new_user_with_utf8_encoded_fullname_should_be_decoded
  899. assert_difference 'User.count' do
  900. issue = submit_email(
  901. 'fullname_of_sender_as_utf8_encoded.eml',
  902. :issue => {:project => 'ecookbook'},
  903. :unknown_user => 'create'
  904. )
  905. end
  906. user = User.order('id DESC').first
  907. assert_equal "foo@example.org", user.mail
  908. str1 = "\xc3\x84\xc3\xa4".force_encoding('UTF-8')
  909. str2 = "\xc3\x96\xc3\xb6".force_encoding('UTF-8')
  910. assert_equal str1, user.firstname
  911. assert_equal str2, user.lastname
  912. end
  913. def test_extract_options_from_env_should_return_options
  914. options = MailHandler.extract_options_from_env({
  915. 'tracker' => 'defect',
  916. 'project' => 'foo',
  917. 'unknown_user' => 'create'
  918. })
  919. assert_equal({
  920. :issue => {:tracker => 'defect', :project => 'foo'},
  921. :unknown_user => 'create'
  922. }, options)
  923. end
  924. def test_safe_receive_should_rescue_exceptions_and_return_false
  925. MailHandler.stubs(:receive).raises(Exception.new "Something went wrong")
  926. assert_equal false, MailHandler.safe_receive
  927. end
  928. private
  929. def submit_email(filename, options={})
  930. raw = IO.read(File.join(FIXTURES_PATH, filename))
  931. yield raw if block_given?
  932. MailHandler.receive(raw, options)
  933. end
  934. def assert_issue_created(issue)
  935. assert issue.is_a?(Issue)
  936. assert !issue.new_record?
  937. issue.reload
  938. end
  939. end