mlist 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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