To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 9c / 9cd7e94ac195fc7d935cfe5b519a4f1a68e73a19.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (22 KB)

1
# encoding: utf-8
2
#
3
# Redmine - project management software
4
# Copyright (C) 2006-2011  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

    
20
require File.expand_path('../../test_helper', __FILE__)
21

    
22
class MailHandlerTest < ActiveSupport::TestCase
23
  fixtures :users, :projects,
24
                   :enabled_modules,
25
                   :roles,
26
                   :members,
27
                   :member_roles,
28
                   :users,
29
                   :issues,
30
                   :issue_statuses,
31
                   :workflows,
32
                   :trackers,
33
                   :projects_trackers,
34
                   :versions,
35
                   :enumerations,
36
                   :issue_categories,
37
                   :custom_fields,
38
                   :custom_fields_trackers,
39
                   :custom_fields_projects,
40
                   :boards,
41
                   :messages
42

    
43
  FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
44

    
45
  def setup
46
    ActionMailer::Base.deliveries.clear
47
    Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
48
  end
49

    
50
  def test_add_issue
51
    ActionMailer::Base.deliveries.clear
52
    # This email contains: 'Project: onlinestore'
53
    issue = submit_email('ticket_on_given_project.eml')
54
    assert issue.is_a?(Issue)
55
    assert !issue.new_record?
56
    issue.reload
57
    assert_equal Project.find(2), issue.project
58
    assert_equal issue.project.trackers.first, issue.tracker
59
    assert_equal 'New ticket on a given project', issue.subject
60
    assert_equal User.find_by_login('jsmith'), issue.author
61
    assert_equal IssueStatus.find_by_name('Resolved'), issue.status
62
    assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
63
    assert_equal '2010-01-01', issue.start_date.to_s
64
    assert_equal '2010-12-31', issue.due_date.to_s
65
    assert_equal User.find_by_login('jsmith'), issue.assigned_to
66
    assert_equal Version.find_by_name('Alpha'), issue.fixed_version
67
    assert_equal 2.5, issue.estimated_hours
68
    assert_equal 30, issue.done_ratio
69
    assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
70
    # keywords should be removed from the email body
71
    assert !issue.description.match(/^Project:/i)
72
    assert !issue.description.match(/^Status:/i)
73
    assert !issue.description.match(/^Start Date:/i)
74
    # Email notification should be sent
75
    mail = ActionMailer::Base.deliveries.last
76
    assert_not_nil mail
77
    assert mail.subject.include?('New ticket on a given project')
78
  end
79

    
80
  def test_add_issue_with_default_tracker
81
    # This email contains: 'Project: onlinestore'
82
    issue = submit_email('ticket_on_given_project.eml', :issue => {:tracker => 'Support request'})
83
    assert issue.is_a?(Issue)
84
    assert !issue.new_record?
85
    issue.reload
86
    assert_equal 'Support request', issue.tracker.name
87
  end
88

    
89
  def test_add_issue_with_status
90
    # This email contains: 'Project: onlinestore' and 'Status: Resolved'
91
    issue = submit_email('ticket_on_given_project.eml')
92
    assert issue.is_a?(Issue)
93
    assert !issue.new_record?
94
    issue.reload
95
    assert_equal Project.find(2), issue.project
96
    assert_equal IssueStatus.find_by_name("Resolved"), issue.status
97
  end
98

    
99
  def test_add_issue_with_attributes_override
100
    issue = submit_email('ticket_with_attributes.eml', :allow_override => 'tracker,category,priority')
101
    assert issue.is_a?(Issue)
102
    assert !issue.new_record?
103
    issue.reload
104
    assert_equal 'New ticket on a given project', issue.subject
105
    assert_equal User.find_by_login('jsmith'), issue.author
106
    assert_equal Project.find(2), issue.project
107
    assert_equal 'Feature request', issue.tracker.to_s
108
    assert_equal 'Stock management', issue.category.to_s
109
    assert_equal 'Urgent', issue.priority.to_s
110
    assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
111
  end
112

    
113
  def test_add_issue_with_group_assignment
114
    with_settings :issue_group_assignment => '1' do
115
      issue = submit_email('ticket_on_given_project.eml') do |email|
116
        email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
117
      end
118
      assert issue.is_a?(Issue)
119
      assert !issue.new_record?
120
      issue.reload
121
      assert_equal Group.find(11), issue.assigned_to
122
    end
123
  end
124

    
125
  def test_add_issue_with_partial_attributes_override
126
    issue = submit_email('ticket_with_attributes.eml', :issue => {:priority => 'High'}, :allow_override => ['tracker'])
127
    assert issue.is_a?(Issue)
128
    assert !issue.new_record?
129
    issue.reload
130
    assert_equal 'New ticket on a given project', issue.subject
131
    assert_equal User.find_by_login('jsmith'), issue.author
132
    assert_equal Project.find(2), issue.project
133
    assert_equal 'Feature request', issue.tracker.to_s
134
    assert_nil issue.category
135
    assert_equal 'High', issue.priority.to_s
136
    assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
137
  end
138

    
139
  def test_add_issue_with_spaces_between_attribute_and_separator
140
    issue = submit_email('ticket_with_spaces_between_attribute_and_separator.eml', :allow_override => 'tracker,category,priority')
141
    assert issue.is_a?(Issue)
142
    assert !issue.new_record?
143
    issue.reload
144
    assert_equal 'New ticket on a given project', issue.subject
145
    assert_equal User.find_by_login('jsmith'), issue.author
146
    assert_equal Project.find(2), issue.project
147
    assert_equal 'Feature request', issue.tracker.to_s
148
    assert_equal 'Stock management', issue.category.to_s
149
    assert_equal 'Urgent', issue.priority.to_s
150
    assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
151
  end
152

    
153
  def test_add_issue_with_attachment_to_specific_project
154
    issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
155
    assert issue.is_a?(Issue)
156
    assert !issue.new_record?
157
    issue.reload
158
    assert_equal 'Ticket created by email with attachment', issue.subject
159
    assert_equal User.find_by_login('jsmith'), issue.author
160
    assert_equal Project.find(2), issue.project
161
    assert_equal 'This is  a new ticket with attachments', issue.description
162
    # Attachment properties
163
    assert_equal 1, issue.attachments.size
164
    assert_equal 'Paella.jpg', issue.attachments.first.filename
165
    assert_equal 'image/jpeg', issue.attachments.first.content_type
166
    assert_equal 10790, issue.attachments.first.filesize
167
  end
168

    
169
  def test_add_issue_with_custom_fields
170
    issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
171
    assert issue.is_a?(Issue)
172
    assert !issue.new_record?
173
    issue.reload
174
    assert_equal 'New ticket with custom field values', issue.subject
175
    assert_equal 'Value for a custom field', issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
176
    assert !issue.description.match(/^searchable field:/i)
177
  end
178

    
179
  def test_add_issue_with_cc
180
    issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
181
    assert issue.is_a?(Issue)
182
    assert !issue.new_record?
183
    issue.reload
184
    assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
185
    assert_equal 1, issue.watcher_user_ids.size
186
  end
187

    
188
  def test_add_issue_by_unknown_user
189
    assert_no_difference 'User.count' do
190
      assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'})
191
    end
192
  end
193

    
194
  def test_add_issue_by_anonymous_user
195
    Role.anonymous.add_permission!(:add_issues)
196
    assert_no_difference 'User.count' do
197
      issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
198
      assert issue.is_a?(Issue)
199
      assert issue.author.anonymous?
200
    end
201
  end
202

    
203
  def test_add_issue_by_anonymous_user_with_no_from_address
204
    Role.anonymous.add_permission!(:add_issues)
205
    assert_no_difference 'User.count' do
206
      issue = submit_email('ticket_by_empty_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'accept')
207
      assert issue.is_a?(Issue)
208
      assert issue.author.anonymous?
209
    end
210
  end
211

    
212
  def test_add_issue_by_anonymous_user_on_private_project
213
    Role.anonymous.add_permission!(:add_issues)
214
    assert_no_difference 'User.count' do
215
      assert_no_difference 'Issue.count' do
216
        assert_equal false, submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'onlinestore'}, :unknown_user => 'accept')
217
      end
218
    end
219
  end
220

    
221
  def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
222
    assert_no_difference 'User.count' do
223
      assert_difference 'Issue.count' do
224
        issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'onlinestore'}, :no_permission_check => '1', :unknown_user => 'accept')
225
        assert issue.is_a?(Issue)
226
        assert issue.author.anonymous?
227
        assert !issue.project.is_public?
228
        assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
229
      end
230
    end
231
  end
232

    
233
  def test_add_issue_by_created_user
234
    Setting.default_language = 'en'
235
    assert_difference 'User.count' do
236
      issue = submit_email('ticket_by_unknown_user.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
237
      assert issue.is_a?(Issue)
238
      assert issue.author.active?
239
      assert_equal 'john.doe@somenet.foo', issue.author.mail
240
      assert_equal 'John', issue.author.firstname
241
      assert_equal 'Doe', issue.author.lastname
242

    
243
      # account information
244
      email = ActionMailer::Base.deliveries.first
245
      assert_not_nil email
246
      assert email.subject.include?('account activation')
247
      login = email.body.match(/\* Login: (.*)$/)[1]
248
      password = email.body.match(/\* Password: (.*)$/)[1]
249
      assert_equal issue.author, User.try_to_login(login, password)
250
    end
251
  end
252

    
253
  def test_add_issue_without_from_header
254
    Role.anonymous.add_permission!(:add_issues)
255
    assert_equal false, submit_email('ticket_without_from_header.eml')
256
  end
257

    
258
  def test_add_issue_with_invalid_attributes
259
    issue = submit_email('ticket_with_invalid_attributes.eml', :allow_override => 'tracker,category,priority')
260
    assert issue.is_a?(Issue)
261
    assert !issue.new_record?
262
    issue.reload
263
    assert_nil issue.assigned_to
264
    assert_nil issue.start_date
265
    assert_nil issue.due_date
266
    assert_equal 0, issue.done_ratio
267
    assert_equal 'Normal', issue.priority.to_s
268
    assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
269
  end
270

    
271
  def test_add_issue_with_localized_attributes
272
    User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
273
    issue = submit_email('ticket_with_localized_attributes.eml', :allow_override => 'tracker,category,priority')
274
    assert issue.is_a?(Issue)
275
    assert !issue.new_record?
276
    issue.reload
277
    assert_equal 'New ticket on a given project', issue.subject
278
    assert_equal User.find_by_login('jsmith'), issue.author
279
    assert_equal Project.find(2), issue.project
280
    assert_equal 'Feature request', issue.tracker.to_s
281
    assert_equal 'Stock management', issue.category.to_s
282
    assert_equal 'Urgent', issue.priority.to_s
283
    assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
284
  end
285

    
286
  def test_add_issue_with_japanese_keywords
287
    tracker = Tracker.create!(:name => '開発')
288
    Project.find(1).trackers << tracker
289
    issue = submit_email('japanese_keywords_iso_2022_jp.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'tracker')
290
    assert_kind_of Issue, issue
291
    assert_equal tracker, issue.tracker
292
  end
293

    
294
  def test_add_issue_from_apple_mail
295
    issue = submit_email('apple_mail_with_attachment.eml', :issue => {:project => 'ecookbook'})
296
    assert_kind_of Issue, issue
297
    assert_equal 1, issue.attachments.size
298

    
299
    attachment = issue.attachments.first
300
    assert_equal 'paella.jpg', attachment.filename
301
    assert_equal 10790, attachment.filesize
302
  end
303

    
304
  def test_should_ignore_emails_from_emission_address
305
    Role.anonymous.add_permission!(:add_issues)
306
    assert_no_difference 'User.count' do
307
      assert_equal false, submit_email('ticket_from_emission_address.eml', :issue => {:project => 'ecookbook'}, :unknown_user => 'create')
308
    end
309
  end
310

    
311
  def test_add_issue_should_send_email_notification
312
    Setting.notified_events = ['issue_added']
313
    ActionMailer::Base.deliveries.clear
314
    # This email contains: 'Project: onlinestore'
315
    issue = submit_email('ticket_on_given_project.eml')
316
    assert issue.is_a?(Issue)
317
    assert_equal 1, ActionMailer::Base.deliveries.size
318
  end
319

    
320
  def test_update_issue
321
    journal = submit_email('ticket_reply.eml')
322
    assert journal.is_a?(Journal)
323
    assert_equal User.find_by_login('jsmith'), journal.user
324
    assert_equal Issue.find(2), journal.journalized
325
    assert_match /This is reply/, journal.notes
326
    assert_equal 'Feature request', journal.issue.tracker.name
327
  end
328

    
329
  def test_update_issue_with_attribute_changes
330
    # This email contains: 'Status: Resolved'
331
    journal = submit_email('ticket_reply_with_status.eml')
332
    assert journal.is_a?(Journal)
333
    issue = Issue.find(journal.issue.id)
334
    assert_equal User.find_by_login('jsmith'), journal.user
335
    assert_equal Issue.find(2), journal.journalized
336
    assert_match /This is reply/, journal.notes
337
    assert_equal 'Feature request', journal.issue.tracker.name
338
    assert_equal IssueStatus.find_by_name("Resolved"), issue.status
339
    assert_equal '2010-01-01', issue.start_date.to_s
340
    assert_equal '2010-12-31', issue.due_date.to_s
341
    assert_equal User.find_by_login('jsmith'), issue.assigned_to
342
    assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
343
    # keywords should be removed from the email body
344
    assert !journal.notes.match(/^Status:/i)
345
    assert !journal.notes.match(/^Start Date:/i)
346
  end
347

    
348
  def test_update_issue_with_attachment
349
    assert_difference 'Journal.count' do
350
      assert_difference 'JournalDetail.count' do
351
        assert_difference 'Attachment.count' do
352
          assert_no_difference 'Issue.count' do
353
            journal = submit_email('ticket_with_attachment.eml') do |raw|
354
              raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
355
            end
356
          end
357
        end
358
      end
359
    end
360
    journal = Journal.first(:order => 'id DESC')
361
    assert_equal Issue.find(2), journal.journalized
362
    assert_equal 1, journal.details.size
363

    
364
    detail = journal.details.first
365
    assert_equal 'attachment', detail.property
366
    assert_equal 'Paella.jpg', detail.value
367
  end
368

    
369
  def test_update_issue_should_send_email_notification
370
    ActionMailer::Base.deliveries.clear
371
    journal = submit_email('ticket_reply.eml')
372
    assert journal.is_a?(Journal)
373
    assert_equal 1, ActionMailer::Base.deliveries.size
374
  end
375

    
376
  def test_update_issue_should_not_set_defaults
377
    journal = submit_email('ticket_reply.eml', :issue => {:tracker => 'Support request', :priority => 'High'})
378
    assert journal.is_a?(Journal)
379
    assert_match /This is reply/, journal.notes
380
    assert_equal 'Feature request', journal.issue.tracker.name
381
    assert_equal 'Normal', journal.issue.priority.name
382
  end
383

    
384
  def test_reply_to_a_message
385
    m = submit_email('message_reply.eml')
386
    assert m.is_a?(Message)
387
    assert !m.new_record?
388
    m.reload
389
    assert_equal 'Reply via email', m.subject
390
    # The email replies to message #2 which is part of the thread of message #1
391
    assert_equal Message.find(1), m.parent
392
  end
393

    
394
  def test_reply_to_a_message_by_subject
395
    m = submit_email('message_reply_by_subject.eml')
396
    assert m.is_a?(Message)
397
    assert !m.new_record?
398
    m.reload
399
    assert_equal 'Reply to the first post', m.subject
400
    assert_equal Message.find(1), m.parent
401
  end
402

    
403
  def test_should_strip_tags_of_html_only_emails
404
    issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
405
    assert issue.is_a?(Issue)
406
    assert !issue.new_record?
407
    issue.reload
408
    assert_equal 'HTML email', issue.subject
409
    assert_equal 'This is a html-only email.', issue.description
410
  end
411

    
412
  context "truncate emails based on the Setting" do
413
    context "with no setting" do
414
      setup do
415
        Setting.mail_handler_body_delimiters = ''
416
      end
417

    
418
      should "add the entire email into the issue" do
419
        issue = submit_email('ticket_on_given_project.eml')
420
        assert_issue_created(issue)
421
        assert issue.description.include?('---')
422
        assert issue.description.include?('This paragraph is after the delimiter')
423
      end
424
    end
425

    
426
    context "with a single string" do
427
      setup do
428
        Setting.mail_handler_body_delimiters = '---'
429
      end
430
      should "truncate the email at the delimiter for the issue" do
431
        issue = submit_email('ticket_on_given_project.eml')
432
        assert_issue_created(issue)
433
        assert issue.description.include?('This paragraph is before delimiters')
434
        assert issue.description.include?('--- This line starts with a delimiter')
435
        assert !issue.description.match(/^---$/)
436
        assert !issue.description.include?('This paragraph is after the delimiter')
437
      end
438
    end
439

    
440
    context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
441
      setup do
442
        Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
443
      end
444
      should "truncate the email at the delimiter with the quoted reply symbols (>)" do
445
        journal = submit_email('issue_update_with_quoted_reply_above.eml')
446
        assert journal.is_a?(Journal)
447
        assert journal.notes.include?('An update to the issue by the sender.')
448
        assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
449
        assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
450
      end
451
    end
452

    
453
    context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
454
      setup do
455
        Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
456
      end
457
      should "truncate the email at the delimiter with the quoted reply symbols (>)" do
458
        journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
459
        assert journal.is_a?(Journal)
460
        assert journal.notes.include?('An update to the issue by the sender.')
461
        assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
462
        assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
463
      end
464
    end
465

    
466
    context "with multiple strings" do
467
      setup do
468
        Setting.mail_handler_body_delimiters = "---\nBREAK"
469
      end
470
      should "truncate the email at the first delimiter found (BREAK)" do
471
        issue = submit_email('ticket_on_given_project.eml')
472
        assert_issue_created(issue)
473
        assert issue.description.include?('This paragraph is before delimiters')
474
        assert !issue.description.include?('BREAK')
475
        assert !issue.description.include?('This paragraph is between delimiters')
476
        assert !issue.description.match(/^---$/)
477
        assert !issue.description.include?('This paragraph is after the delimiter')
478
      end
479
    end
480
  end
481

    
482
  def test_email_with_long_subject_line
483
    issue = submit_email('ticket_with_long_subject.eml')
484
    assert issue.is_a?(Issue)
485
    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]
486
  end
487

    
488
  def test_new_user_from_attributes_should_return_valid_user
489
    to_test = {
490
      # [address, name] => [login, firstname, lastname]
491
      ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
492
      ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
493
      ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
494
      ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
495
      ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
496
      ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh'],
497
      ['alongemailaddressthatexceedsloginlength@example.net', 'John Smith'] => ['alongemailaddressthatexceedslo', 'John', 'Smith']
498
    }
499

    
500
    to_test.each do |attrs, expected|
501
      user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
502

    
503
      assert user.valid?
504
      assert_equal attrs.first, user.mail
505
      assert_equal expected[0], user.login
506
      assert_equal expected[1], user.firstname
507
      assert_equal expected[2], user.lastname
508
    end
509
  end
510

    
511
  def test_new_user_from_attributes_should_respect_minimum_password_length
512
    with_settings :password_min_length => 15 do
513
      user = MailHandler.new_user_from_attributes('jsmith@example.net')
514
      assert user.valid?
515
      assert user.password.length >= 15
516
    end
517
  end
518
 
519
  def test_new_user_from_attributes_should_use_default_login_if_invalid
520
    MailHandler.new_user_from_attributes('alongemailaddressthatexceedsloginlength-1@example.net').save!
521

    
522
    # another long address that would result in duplicate login
523
    user = MailHandler.new_user_from_attributes('alongemailaddressthatexceedsloginlength-2@example.net')
524
    assert user.valid?
525
    assert user.login =~ /^user[a-f0-9]+$/
526
  end
527

    
528
  private
529

    
530
  def submit_email(filename, options={})
531
    raw = IO.read(File.join(FIXTURES_PATH, filename))
532
    yield raw if block_given?
533
    MailHandler.receive(raw, options)
534
  end
535

    
536
  def assert_issue_created(issue)
537
    assert issue.is_a?(Issue)
538
    assert !issue.new_record?
539
    issue.reload
540
  end
541
end