mlist 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +59 -0
  2. data/README +204 -0
  3. data/Rakefile +27 -0
  4. data/TODO +36 -0
  5. data/VERSION.yml +4 -0
  6. data/lib/mlist/email.rb +69 -0
  7. data/lib/mlist/email_post.rb +126 -0
  8. data/lib/mlist/email_server/base.rb +33 -0
  9. data/lib/mlist/email_server/default.rb +31 -0
  10. data/lib/mlist/email_server/fake.rb +16 -0
  11. data/lib/mlist/email_server/pop.rb +28 -0
  12. data/lib/mlist/email_server/smtp.rb +24 -0
  13. data/lib/mlist/email_server.rb +2 -0
  14. data/lib/mlist/email_subscriber.rb +6 -0
  15. data/lib/mlist/list.rb +183 -0
  16. data/lib/mlist/mail_list.rb +277 -0
  17. data/lib/mlist/manager/database.rb +48 -0
  18. data/lib/mlist/manager/notifier.rb +31 -0
  19. data/lib/mlist/manager.rb +30 -0
  20. data/lib/mlist/message.rb +150 -0
  21. data/lib/mlist/server.rb +62 -0
  22. data/lib/mlist/thread.rb +98 -0
  23. data/lib/mlist/util/email_helpers.rb +155 -0
  24. data/lib/mlist/util/header_sanitizer.rb +71 -0
  25. data/lib/mlist/util/quoting.rb +70 -0
  26. data/lib/mlist/util/tmail_builder.rb +42 -0
  27. data/lib/mlist/util/tmail_methods.rb +138 -0
  28. data/lib/mlist/util.rb +12 -0
  29. data/lib/mlist.rb +46 -0
  30. data/lib/pop_ssl.rb +999 -0
  31. data/rails/init.rb +22 -0
  32. data/spec/fixtures/schema.rb +94 -0
  33. data/spec/integration/date_formats_spec.rb +12 -0
  34. data/spec/integration/mlist_spec.rb +232 -0
  35. data/spec/integration/pop_email_server_spec.rb +22 -0
  36. data/spec/integration/proof_spec.rb +74 -0
  37. data/spec/matchers/equal_tmail.rb +53 -0
  38. data/spec/matchers/have_address.rb +48 -0
  39. data/spec/matchers/have_header.rb +104 -0
  40. data/spec/models/email_post_spec.rb +100 -0
  41. data/spec/models/email_server/base_spec.rb +11 -0
  42. data/spec/models/email_spec.rb +54 -0
  43. data/spec/models/mail_list_spec.rb +469 -0
  44. data/spec/models/message_spec.rb +109 -0
  45. data/spec/models/thread_spec.rb +83 -0
  46. data/spec/models/util/email_helpers_spec.rb +47 -0
  47. data/spec/models/util/header_sanitizer_spec.rb +19 -0
  48. data/spec/models/util/quoting_spec.rb +96 -0
  49. data/spec/spec_helper.rb +76 -0
  50. metadata +103 -0
@@ -0,0 +1,109 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MList::Message do
4
+ include MList::Util::EmailHelpers
5
+
6
+ before do
7
+ @tmail = tmail_fixture('single_list')
8
+ @email = MList::Email.new(:tmail => @tmail)
9
+ @message = MList::Message.new(:email => @email)
10
+ end
11
+
12
+ it 'should capture the mailer header' do
13
+ @message.mailer.should == 'Apple Mail (2.929.2)'
14
+ end
15
+
16
+ it 'should capture the subject' do
17
+ @message.subject.should == 'Test'
18
+ end
19
+
20
+ it 'should not modify the original email' do
21
+ mock(@email)
22
+ @message.subject = 'modified'
23
+ @message.mailer = 'modified'
24
+ @message.identifier = 'modified'
25
+ end
26
+
27
+ it 'should answer a subject suitable for replies' do
28
+ @message.subject = '[List Label] The new Chrome Browser from Google'
29
+ @message.subject_for_reply.should == 'Re: [List Label] The new Chrome Browser from Google'
30
+
31
+ @message.subject = 'Re: [List Label] The new Chrome Browser from Google'
32
+ @message.subject_for_reply.should == 'Re: [List Label] The new Chrome Browser from Google'
33
+ end
34
+
35
+ it 'should save the associated email' do
36
+ @message.save!
37
+ @message = MList::Message.find(@message.id)
38
+ @message.email.source.should == email_fixture('single_list')
39
+ end
40
+
41
+ it 'should delete the email when message destroyed' do
42
+ @message.save!
43
+ @message.destroy
44
+ MList::Email.exists?(@email).should be_false
45
+ end
46
+
47
+ it 'should not delete the email if other messages reference it' do
48
+ @message.save!
49
+ MList::Message.create!(:mail_list_id => 234234, :email => @email)
50
+ @message.destroy
51
+ MList::Email.exists?(@email).should be_true
52
+ end
53
+ end
54
+
55
+ describe MList::Message, 'text' do
56
+ def message_from_tmail(path)
57
+ tmail = tmail_fixture(path)
58
+ email = MList::Email.new(:tmail => tmail)
59
+ MList::Message.new(:email => email)
60
+ end
61
+
62
+ it 'should work with text/plain' do
63
+ message_from_tmail('content_types/text_plain').text.should == 'Hello there'
64
+ end
65
+
66
+ it 'should work with multipart/alternative, simple' do
67
+ message_from_tmail('content_types/multipart_alternative_simple').text.should ==
68
+ "This is just a simple test.\n\nThis line should be bold.\n\nThis line should be italic."
69
+ end
70
+
71
+ it 'should work with mutltipart/mixed, outlook' do
72
+ message_from_tmail('content_types/multipart_mixed_outlook').text.should ==
73
+ "This is a simple test."
74
+ end
75
+
76
+ it 'should work with multipart/related, no text part' do
77
+ message_from_tmail('content_types/multipart_related_no_text_plain').text.should == %(I don't really have much to say, so I'm going to share some random things I saw today:
78
+
79
+ I saw this guy on twitter.com, and he looks pretty chill:
80
+
81
+ I found this sweet url, and it's not dirty!:
82
+
83
+ I found out that if I call our Skype phone from Skype on my laptop, my laptop will give me the ability to answer the call I am placing. Freaky!
84
+
85
+ Here is what my rating star widget looks like:
86
+
87
+ What's with the dashes and tildes?
88
+
89
+ Yeah, what is going on with that. They don't even match.
90
+ -~----~~----~----~----~----~---~~-~----~------~--~-~-
91
+ vs
92
+ --~--~---~~----~--~----~-----~~~----~---~---~--~-~--~
93
+
94
+
95
+ Good job with this!
96
+
97
+ -Steve)
98
+ end
99
+
100
+ it 'should answer text suitable for reply' do
101
+ message_from_tmail('content_types/text_plain').text_for_reply.should ==
102
+ email_fixture('content_types/text_plain_reply.txt')
103
+ end
104
+
105
+ it 'should answer html suitable for reply' do
106
+ message_from_tmail('content_types/text_plain').html_for_reply.should ==
107
+ email_fixture('content_types/text_plain_reply.html')
108
+ end
109
+ end
@@ -0,0 +1,83 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MList::Thread do
4
+ before do
5
+ @messages = (1..3).map {|i| m = MList::Message.new; stub(m).subject {i.to_s}; m}
6
+ @thread = MList::Thread.new
7
+ stub(@thread).messages {@messages}
8
+ end
9
+
10
+ it 'should answer subject by way of the first message' do
11
+ @thread.subject.should == '1'
12
+ end
13
+
14
+ it 'should have messages counted' do
15
+ MList::Message.reflect_on_association(:thread).counter_cache_column.should == :messages_count
16
+ MList::Thread.column_names.should include('messages_count')
17
+ end
18
+ end
19
+
20
+ describe MList::Thread, 'tree' do
21
+ before do
22
+ @messages = (1..5).map {|i| m = MList::Message.new; m.id = i; m}
23
+ @messages[1].parent_id = @messages[0].id
24
+ @messages[2].parent_id = @messages[1].id
25
+ @messages[3].parent_id = @messages[0].id
26
+ @messages[4].parent_id = @messages[3].id
27
+
28
+ @thread = MList::Thread.new
29
+ stub(@thread).messages {@messages}
30
+
31
+ @tree = @thread.tree
32
+ end
33
+
34
+ it 'should answer the first message as the root of the tree' do
35
+ @tree.should == @messages[0]
36
+ end
37
+
38
+ it 'should connect the nodes into a tree' do
39
+ @tree.children.should == [@messages[1], @messages[3]]
40
+ end
41
+
42
+ it 'should connect next and previous to each node' do
43
+ @tree.previous.should be_nil
44
+ @tree.next.should == @messages[1]
45
+ @tree.next.previous.should == @messages[0]
46
+ @tree.next.next.should == @messages[2]
47
+ @tree.next.next.next.should == @messages[3]
48
+ end
49
+
50
+ it 'should know if a message is the root' do
51
+ @tree.root?.should be_true
52
+ @tree.next.root?.should be_false
53
+ end
54
+
55
+ it 'should know if a message is a leaf' do
56
+ @tree.leaf?.should be_false
57
+ @tree.next.leaf?.should be_false
58
+ @tree.next.next.leaf?.should be_true
59
+ end
60
+
61
+ it 'should answer when a message is last in the thread' do
62
+ @thread.first?(@messages[0]).should be_true
63
+ @thread.first?(@messages[1]).should be_false
64
+ @thread.first?(@messages[2]).should be_false
65
+ end
66
+
67
+ it 'should answer when a message is last in the thread' do
68
+ @thread.last?(@messages[0]).should be_false
69
+ @thread.last?(@messages[1]).should be_false
70
+ @thread.last?(@messages[4]).should be_true
71
+ end
72
+
73
+ it 'should answer the message next to given' do
74
+ @thread.next(@messages[0]).should == @messages[1]
75
+ @thread.next(@messages[2]).should == @messages[3]
76
+ @thread.next(@messages[4]).should be_nil
77
+ end
78
+
79
+ it 'should answer the message previous to given' do
80
+ @thread.previous(@messages[0]).should be_nil
81
+ @thread.previous(@messages[2]).should == @messages[1]
82
+ end
83
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe MList::Util::EmailHelpers do
4
+ include MList::Util::EmailHelpers
5
+
6
+ %w(nothing_special ascii_art reply_quoting reply_quoting_deeper bullets bullets_leading_space).each do |text_source_name|
7
+ specify "text_to_html should convert #{text_source_name}" do
8
+ source_text = text_fixture(text_source_name)
9
+ expected_text = text_fixture("#{text_source_name}.html")
10
+ text_to_html(source_text).should == expected_text
11
+ end
12
+ end
13
+
14
+ specify 'text_to_quoted should prepend >' do
15
+ text_to_quoted(text_fixture('nothing_special')).should == text_fixture('nothing_special_quoted')
16
+ end
17
+
18
+ describe 'remove_regard' do
19
+ it 'should remove regardless of case' do
20
+ remove_regard('Re: [Label] Subject').should == '[Label] Subject'
21
+ remove_regard('RE: [Label] Subject').should == '[Label] Subject'
22
+ end
23
+
24
+ it 'should not bother [] labels when multiple re:' do
25
+ remove_regard('Re: [Label] Re: Subject').should == '[Label] Subject'
26
+ end
27
+
28
+ it 'should remove multiple re:' do
29
+ remove_regard('Re: Re: Test').should == 'Test'
30
+ remove_regard('Re: Re: Subject').should == 'Subject'
31
+ end
32
+ end
33
+
34
+ describe 'html_to_text' do
35
+ it 'should handle real life example' do
36
+ html_to_text(html_fixture('real_life')).should == html_fixture('real_life.txt')
37
+ end
38
+
39
+ it 'should handle lists' do
40
+ html_to_text('<p>Fruits</p> <ul><li>Apples</li><li>Oranges</li><li>Bananas</li></ul>').should == %{Fruits\n\n * Apples\n\n * Oranges\n\n * Bananas}
41
+ end
42
+
43
+ it 'should handle lots of non-breaking space' do
44
+ html_to_text(html_fixture('nbsp')).should == html_fixture('nbsp.txt')
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe MList::Util::HeaderSanitizerHash do
4
+ before do
5
+ @sanitizer = MList::Util::HeaderSanitizerHash.new
6
+ end
7
+
8
+ %w(to cc bcc from reply-to).each do |header|
9
+ it %Q{should escape " and \\ in address phrase for #{header}} do
10
+ @sanitizer[header].call('UTF-8', '"Johnny " Dangerously \" <johnny@nomail.net>').should == ['"Johnny \" Dangerously \\\\" <johnny@nomail.net>']
11
+ end
12
+ end
13
+
14
+ %w(sender errors-to).each do |header|
15
+ it %Q{should escape " in address phrase for #{header}} do
16
+ @sanitizer[header].call('UTF-8', '"Johnny " Dangerously \" <johnny@nomail.net>').should == '"Johnny \" Dangerously \\\\" <johnny@nomail.net>'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ require 'tempfile'
4
+
5
+ describe MList::Util::Quoting do
6
+ # Move some tests from TMAIL here
7
+ it 'should unquote quoted printable' do
8
+ a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
9
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
10
+ b.should == "[166417] Bekr\303\246ftelse fra Rejsefeber"
11
+ end
12
+
13
+ it 'should unquote base64' do
14
+ a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
15
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
16
+ b.should == "[166417] Bekr\303\246ftelse fra Rejsefeber"
17
+ end
18
+
19
+ it 'should unquote without charset' do
20
+ a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
21
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
22
+ b.should == "[166417]_Bekr=E6ftelse_fra_Rejsefeber"
23
+ end
24
+
25
+ it 'should unqoute multiple' do
26
+ a ="=?utf-8?q?Re=3A_=5B12=5D_=23137=3A_Inkonsistente_verwendung_von_=22Hin?==?utf-8?b?enVmw7xnZW4i?="
27
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
28
+ b.should == "Re: [12] #137: Inkonsistente verwendung von \"Hinzuf\303\274gen\""
29
+ end
30
+
31
+ it 'should unqoute in the middle' do
32
+ a ="Re: Photos =?ISO-8859-1?Q?Brosch=FCre_Rand?="
33
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
34
+ b.should == "Re: Photos Brosch\303\274re Rand"
35
+ end
36
+
37
+ it 'should unqoute iso' do
38
+ a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?="
39
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'iso-8859-1')
40
+ expected = "Brosch\374re Rand"
41
+ expected.force_encoding 'iso-8859-1' if expected.respond_to?(:force_encoding)
42
+ b.should == expected
43
+ end
44
+
45
+ it 'should quote multibyte chars' do
46
+ original = "\303\246 \303\270 and \303\245"
47
+ original.force_encoding('ASCII-8BIT') if original.respond_to?(:force_encoding)
48
+
49
+ result = execute_in_sandbox(<<-CODE)
50
+ $:.unshift(File.dirname(__FILE__) + "/../../../lib/")
51
+ $KCODE = 'u'
52
+ require 'jcode'
53
+ require 'mlist/util/quoting'
54
+ include MList::Util::Quoting
55
+ quoted_printable("UTF-8", #{original.inspect})
56
+ CODE
57
+
58
+ unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
59
+ unquoted.should == original
60
+ end
61
+
62
+ # test an email that has been created using \r\n newlines, instead of
63
+ # \n newlines.
64
+ it 'should email quoted with 0d0a' do
65
+ mail = TMail::Mail.parse(IO.read("#{SPEC_ROOT}/fixtures/email/raw_email_quoted_with_0d0a"))
66
+ mail.body.should match(%r{Elapsed time})
67
+ end
68
+
69
+ it 'should email with partially quoted subject' do
70
+ mail = TMail::Mail.parse(IO.read("#{SPEC_ROOT}/fixtures/email/raw_email_with_partially_quoted_subject"))
71
+ mail.subject.should == "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail"
72
+ end
73
+
74
+ private
75
+ # This whole thing *could* be much simpler, but I don't think Tempfile,
76
+ # popen and others exist on all platforms (like Windows).
77
+ def execute_in_sandbox(code)
78
+ test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
79
+ res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
80
+
81
+ File.open(test_name, "w+") do |file|
82
+ file.write(<<-CODE)
83
+ block = Proc.new do
84
+ #{code}
85
+ end
86
+ puts block.call
87
+ CODE
88
+ end
89
+
90
+ system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
91
+ File.read(res_name).chomp
92
+ ensure
93
+ File.delete(test_name) rescue nil
94
+ File.delete(res_name) rescue nil
95
+ end
96
+ end
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'rr'
4
+ require 'ostruct'
5
+
6
+ Spec::Runner.configure do |config|
7
+ config.mock_with :rr
8
+ end
9
+
10
+ SPEC_ROOT = File.expand_path(File.dirname(__FILE__))
11
+ $LOAD_PATH.unshift(SPEC_ROOT + '/../lib')
12
+ Dir[SPEC_ROOT + '/matchers/*.rb'].each { |path| require path }
13
+
14
+ require 'active_record'
15
+ SQLITE_DATABASE = "#{SPEC_ROOT}/sqlite3.db"
16
+ ActiveRecord::Base.silence do
17
+ ActiveRecord::Base.configurations = {'test' => {
18
+ 'adapter' => 'sqlite3',
19
+ 'database' => SQLITE_DATABASE
20
+ }}
21
+ ActiveRecord::Base.establish_connection 'test'
22
+ load "#{SPEC_ROOT}/fixtures/schema.rb"
23
+ end
24
+
25
+ require 'dataset'
26
+ class Spec::Example::ExampleGroup
27
+ include Dataset
28
+ datasets_directory "#{SPEC_ROOT}/datasets"
29
+ end
30
+
31
+ # Fixture helpers
32
+ def email_fixtures_path(path)
33
+ File.join(SPEC_ROOT, 'fixtures/email', path)
34
+ end
35
+
36
+ def email_fixture(path)
37
+ File.read(email_fixtures_path(path))
38
+ end
39
+
40
+ def tmail_fixture(path, header_changes = {})
41
+ tmail = TMail::Mail.parse(email_fixture(path))
42
+ header_changes.each do |k,v|
43
+ tmail[k] = v
44
+ end
45
+ tmail
46
+ end
47
+
48
+ def html_fixtures_path(path)
49
+ File.join(SPEC_ROOT, 'fixtures/html', path)
50
+ end
51
+
52
+ def html_fixture(path)
53
+ File.read(html_fixtures_path(path))
54
+ end
55
+
56
+ def text_fixtures_path(path)
57
+ File.join(SPEC_ROOT, 'fixtures/text', path)
58
+ end
59
+
60
+ def text_fixture(path)
61
+ File.read(text_fixtures_path(path))
62
+ end
63
+
64
+ # To see the output of an email in your client, this will use sendmail to
65
+ # deliver the email to the given address. It shouldn't be sent to the
66
+ # addresses in to: cc: or bcc:, I hope.
67
+ #
68
+ def visualize_email(email, recipient_address)
69
+ tf = Tempfile.new('email_visualize')
70
+ tf.puts email.to_s
71
+ tf.close
72
+ `cat #{tf.path} | sendmail -t #{recipient_address}`
73
+ end
74
+
75
+ require 'mlist'
76
+ require 'mlist/email_server/fake'
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mlist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.9
5
+ platform: ruby
6
+ authors:
7
+ - Adam Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-21 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Ruby mailing list library designed to be integrated into other applications.
17
+ email: adam@thewilliams.ws
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - TODO
25
+ files:
26
+ - CHANGELOG
27
+ - README
28
+ - Rakefile
29
+ - TODO
30
+ - VERSION.yml
31
+ - lib/mlist.rb
32
+ - lib/mlist/email.rb
33
+ - lib/mlist/email_post.rb
34
+ - lib/mlist/email_server.rb
35
+ - lib/mlist/email_server/base.rb
36
+ - lib/mlist/email_server/default.rb
37
+ - lib/mlist/email_server/fake.rb
38
+ - lib/mlist/email_server/pop.rb
39
+ - lib/mlist/email_server/smtp.rb
40
+ - lib/mlist/email_subscriber.rb
41
+ - lib/mlist/list.rb
42
+ - lib/mlist/mail_list.rb
43
+ - lib/mlist/manager.rb
44
+ - lib/mlist/manager/database.rb
45
+ - lib/mlist/manager/notifier.rb
46
+ - lib/mlist/message.rb
47
+ - lib/mlist/server.rb
48
+ - lib/mlist/thread.rb
49
+ - lib/mlist/util.rb
50
+ - lib/mlist/util/email_helpers.rb
51
+ - lib/mlist/util/header_sanitizer.rb
52
+ - lib/mlist/util/quoting.rb
53
+ - lib/mlist/util/tmail_builder.rb
54
+ - lib/mlist/util/tmail_methods.rb
55
+ - lib/pop_ssl.rb
56
+ - rails/init.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/aiwilliams/mlist
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --charset=UTF-8
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.5
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: A Ruby mailing list library designed to be integrated into other applications.
85
+ test_files:
86
+ - spec/fixtures/schema.rb
87
+ - spec/integration/date_formats_spec.rb
88
+ - spec/integration/mlist_spec.rb
89
+ - spec/integration/pop_email_server_spec.rb
90
+ - spec/integration/proof_spec.rb
91
+ - spec/matchers/equal_tmail.rb
92
+ - spec/matchers/have_address.rb
93
+ - spec/matchers/have_header.rb
94
+ - spec/models/email_post_spec.rb
95
+ - spec/models/email_server/base_spec.rb
96
+ - spec/models/email_spec.rb
97
+ - spec/models/mail_list_spec.rb
98
+ - spec/models/message_spec.rb
99
+ - spec/models/thread_spec.rb
100
+ - spec/models/util/email_helpers_spec.rb
101
+ - spec/models/util/header_sanitizer_spec.rb
102
+ - spec/models/util/quoting_spec.rb
103
+ - spec/spec_helper.rb