mlist 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +59 -0
- data/README +204 -0
- data/Rakefile +27 -0
- data/TODO +36 -0
- data/VERSION.yml +4 -0
- data/lib/mlist/email.rb +69 -0
- data/lib/mlist/email_post.rb +126 -0
- data/lib/mlist/email_server/base.rb +33 -0
- data/lib/mlist/email_server/default.rb +31 -0
- data/lib/mlist/email_server/fake.rb +16 -0
- data/lib/mlist/email_server/pop.rb +28 -0
- data/lib/mlist/email_server/smtp.rb +24 -0
- data/lib/mlist/email_server.rb +2 -0
- data/lib/mlist/email_subscriber.rb +6 -0
- data/lib/mlist/list.rb +183 -0
- data/lib/mlist/mail_list.rb +277 -0
- data/lib/mlist/manager/database.rb +48 -0
- data/lib/mlist/manager/notifier.rb +31 -0
- data/lib/mlist/manager.rb +30 -0
- data/lib/mlist/message.rb +150 -0
- data/lib/mlist/server.rb +62 -0
- data/lib/mlist/thread.rb +98 -0
- data/lib/mlist/util/email_helpers.rb +155 -0
- data/lib/mlist/util/header_sanitizer.rb +71 -0
- data/lib/mlist/util/quoting.rb +70 -0
- data/lib/mlist/util/tmail_builder.rb +42 -0
- data/lib/mlist/util/tmail_methods.rb +138 -0
- data/lib/mlist/util.rb +12 -0
- data/lib/mlist.rb +46 -0
- data/lib/pop_ssl.rb +999 -0
- data/rails/init.rb +22 -0
- data/spec/fixtures/schema.rb +94 -0
- data/spec/integration/date_formats_spec.rb +12 -0
- data/spec/integration/mlist_spec.rb +232 -0
- data/spec/integration/pop_email_server_spec.rb +22 -0
- data/spec/integration/proof_spec.rb +74 -0
- data/spec/matchers/equal_tmail.rb +53 -0
- data/spec/matchers/have_address.rb +48 -0
- data/spec/matchers/have_header.rb +104 -0
- data/spec/models/email_post_spec.rb +100 -0
- data/spec/models/email_server/base_spec.rb +11 -0
- data/spec/models/email_spec.rb +54 -0
- data/spec/models/mail_list_spec.rb +469 -0
- data/spec/models/message_spec.rb +109 -0
- data/spec/models/thread_spec.rb +83 -0
- data/spec/models/util/email_helpers_spec.rb +47 -0
- data/spec/models/util/header_sanitizer_spec.rb +19 -0
- data/spec/models/util/quoting_spec.rb +96 -0
- data/spec/spec_helper.rb +76 -0
- metadata +103 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe MList::EmailPost do
|
4
|
+
before do
|
5
|
+
@subscriber = MList::EmailSubscriber.new('john@example.com')
|
6
|
+
@post = MList::EmailPost.new({
|
7
|
+
:subscriber => @subscriber,
|
8
|
+
:subject => "I'm a Program!",
|
9
|
+
:text => "My simple message that isn't too short"
|
10
|
+
})
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should include html and text when both provided' do
|
14
|
+
@post.html = '<p>My simple message</p>'
|
15
|
+
|
16
|
+
tmail = @post.to_tmail
|
17
|
+
tmail.content_type.should == 'multipart/alternative'
|
18
|
+
tmail.parts.size.should == 2
|
19
|
+
|
20
|
+
# The first part should be the least desirable...
|
21
|
+
part = tmail.parts.first
|
22
|
+
part.content_type.should == 'text/plain'
|
23
|
+
|
24
|
+
# And the last should be the best alternative.
|
25
|
+
part = tmail.parts.last
|
26
|
+
part.content_type.should == 'text/html'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should default the mailer to 'MList Client Application'" do
|
30
|
+
@post.to_tmail['x-mailer'].to_s.should == 'MList Client Application'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should use the given mailer' do
|
34
|
+
@post.mailer = 'My Program'
|
35
|
+
@post.to_tmail['x-mailer'].to_s.should == 'My Program'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should use the given subject' do
|
39
|
+
@post.subject.should == "I'm a Program!"
|
40
|
+
@post.to_tmail.subject.should == "I'm a Program!"
|
41
|
+
|
42
|
+
@post.subject = 'My Program'
|
43
|
+
@post.subject.should == 'My Program'
|
44
|
+
@post.to_tmail.subject.should == 'My Program'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should assign the identifier it is in-reply-to and references' do
|
48
|
+
message = MList::Message.new
|
49
|
+
stub(message).identifier { 'blahblah@example.com' }
|
50
|
+
@post.reply_to_message = message
|
51
|
+
@post.to_tmail.in_reply_to.should == ["<blahblah@example.com>"]
|
52
|
+
@post.to_tmail.references.should == ["<blahblah@example.com>"]
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should include the subscriber display name in the from address if subscriber supports it' do
|
56
|
+
stub(@subscriber).display_name { 'Johnny' }
|
57
|
+
@post.to_tmail['from'].to_s.should == "Johnny <john@example.com>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe MList::EmailPost, 'validations' do
|
62
|
+
before do
|
63
|
+
subscriber = MList::EmailSubscriber.new('john@example.com')
|
64
|
+
@post = MList::EmailPost.new({
|
65
|
+
:subscriber => subscriber,
|
66
|
+
:subject => "I'm a Program!",
|
67
|
+
:text => "My simple message that isn't too short"
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should be valid with subject and a few words' do
|
72
|
+
@post.should be_valid
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should require text' do
|
76
|
+
@post.text = ''
|
77
|
+
@post.should_not be_valid
|
78
|
+
@post.errors[:text].should_not be_nil
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should require at least 25 characters of text' do
|
82
|
+
@post.text = 'A' * 24
|
83
|
+
@post.should_not be_valid
|
84
|
+
@post.errors[:text].should_not be_nil
|
85
|
+
|
86
|
+
@post.text = 'A' * 25
|
87
|
+
@post.should be_valid
|
88
|
+
@post.errors[:text].should be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should require subject' do
|
92
|
+
@post.subject = ''
|
93
|
+
@post.should_not be_valid
|
94
|
+
@post.errors[:subject].should_not be_nil
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should not fail to work when asked to generate error messages' do
|
98
|
+
@post.errors.add(:text)
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe MList::EmailServer::Base do
|
4
|
+
before do
|
5
|
+
@email_server = MList::EmailServer::Fake.new(:domain => 'test.host')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should provide unique message id generator' do
|
9
|
+
@email_server.generate_message_id.should match(/([a-f0-9]+-){4,}[a-f0-9]+@test.host/)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe MList::Email do
|
4
|
+
before do
|
5
|
+
@tmail = tmail_fixture('single_list')
|
6
|
+
@email = MList::Email.new(:tmail => @tmail)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should downcase the from address' do
|
10
|
+
@tmail.from = 'ALL_Down_CaSe@NOmail.NET'
|
11
|
+
@email.from_address.should == 'all_down_case@nomail.net'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should downcase list addresses' do
|
15
|
+
@tmail.to = 'ALL_Down_CaSe@NOmail.NET, ALL_Down_CaSe@YESmail.NET'
|
16
|
+
@email.list_addresses.should == %w(all_down_case@nomail.net all_down_case@yesmail.net)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should handle @ in label of addresses' do
|
20
|
+
@tmail = tmail_fixture('at_in_address_label')
|
21
|
+
@email = MList::Email.new(:tmail => @tmail)
|
22
|
+
@email.list_addresses.should == %w(whatever@yesmail.net)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should include the cc field in list addresses, no duplicates' do
|
26
|
+
@tmail.to = 'list_one@example.com'
|
27
|
+
@tmail.cc = 'list_one@example.com, list_two@example.com'
|
28
|
+
@email.list_addresses.should == %w(list_one@example.com list_two@example.com)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should answer the subject of the email' do
|
32
|
+
@email.subject.should == 'Test'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should be careful to save true source of email' do
|
36
|
+
@email = MList::Email.new(:tmail => tmail_fixture('embedded_content'))
|
37
|
+
@email.save!
|
38
|
+
@email.reload.source.should == email_fixture('embedded_content')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should answer the Date of the email, created_at otherwise' do
|
42
|
+
@email.date.should == Time.parse('Mon, 15 Dec 2008 00:38:31 -0500')
|
43
|
+
|
44
|
+
@tmail['date'] = nil
|
45
|
+
stub(Time).now { Time.local(2009,1,1) }
|
46
|
+
@email.date.should == Time.local(2009,1,1)
|
47
|
+
|
48
|
+
stub(Time).now { Time.local(2009,3,1) }
|
49
|
+
@email.date.should == Time.local(2009,1,1)
|
50
|
+
|
51
|
+
@email.created_at = Time.local(2009,2,1)
|
52
|
+
@email.date.should == Time.local(2009,2,1)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,469 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe MList::MailList do
|
4
|
+
class ManagerList
|
5
|
+
include MList::List
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
@manager_list = ManagerList.new
|
10
|
+
stub(@manager_list).label {'Discussions'}
|
11
|
+
stub(@manager_list).address {'list_one@example.com'}
|
12
|
+
stub(@manager_list).list_id {'list_one@example.com'}
|
13
|
+
|
14
|
+
@subscriber_one = MList::EmailSubscriber.new('adam@nomail.net')
|
15
|
+
@subscriber_two = MList::EmailSubscriber.new('john@example.com')
|
16
|
+
stub(@manager_list).subscribers {[@subscriber_one, @subscriber_two]}
|
17
|
+
|
18
|
+
@outgoing_server = MList::EmailServer::Fake.new
|
19
|
+
@mail_list = MList::MailList.create!(
|
20
|
+
:manager_list => @manager_list,
|
21
|
+
:outgoing_server => @outgoing_server)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should not require the manager list be an ActiveRecord type' do
|
25
|
+
@mail_list.list.should == @manager_list
|
26
|
+
@mail_list.manager_list.should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should have messages counted' do
|
30
|
+
MList::Message.reflect_on_association(:mail_list).counter_cache_column.should == :messages_count
|
31
|
+
MList::MailList.column_names.should include('messages_count')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should have threads counted' do
|
35
|
+
MList::Thread.reflect_on_association(:mail_list).counter_cache_column.should == :threads_count
|
36
|
+
MList::MailList.column_names.should include('threads_count')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should delete email not referenced by other lists' do
|
40
|
+
email_in_other = MList::Email.create!(:tmail => tmail_fixture('single_list'))
|
41
|
+
email_not_other = MList::Email.create!(:tmail => tmail_fixture('single_list'))
|
42
|
+
lambda do
|
43
|
+
MList::Message.create!(:mail_list_id => 2342342, :email => email_in_other)
|
44
|
+
@mail_list.process_email(email_in_other, @subscriber_one)
|
45
|
+
@mail_list.process_email(email_not_other, @subscriber_one)
|
46
|
+
end.should change(MList::Message, :count).by(3)
|
47
|
+
@mail_list.destroy
|
48
|
+
MList::Email.exists?(email_in_other).should be_true
|
49
|
+
MList::Email.exists?(email_not_other).should be_false
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'post' do
|
53
|
+
it 'should allow posting a new message to the list' do
|
54
|
+
lambda do
|
55
|
+
lambda do
|
56
|
+
@mail_list.post(
|
57
|
+
:subscriber => @subscriber_one,
|
58
|
+
:subject => "I'm a Program!",
|
59
|
+
:text => 'Are you a programmer or what?'
|
60
|
+
)
|
61
|
+
end.should change(MList::Message, :count).by(1)
|
62
|
+
end.should change(MList::Thread, :count).by(1)
|
63
|
+
|
64
|
+
tmail = @outgoing_server.deliveries.last
|
65
|
+
tmail.subject.should =~ /I'm a Program!/
|
66
|
+
tmail.from.should == ['adam@nomail.net']
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should answer the message for use by the application' do
|
70
|
+
@mail_list.post(
|
71
|
+
:subscriber => @subscriber_one,
|
72
|
+
:subject => "I'm a Program!",
|
73
|
+
:text => 'Are you a programmer or what?'
|
74
|
+
).should be_instance_of(MList::Message)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should allow posting a reply to an existing message' do
|
78
|
+
@mail_list.process_email(MList::Email.new(:tmail => tmail_fixture('single_list')), @subscriber_one)
|
79
|
+
existing_message = @mail_list.messages.last
|
80
|
+
lambda do
|
81
|
+
lambda do
|
82
|
+
@mail_list.post(
|
83
|
+
:reply_to_message => existing_message,
|
84
|
+
:subscriber => @subscriber_one,
|
85
|
+
:text => 'I am a programmer too, dude!'
|
86
|
+
)
|
87
|
+
end.should change(MList::Message, :count).by(1)
|
88
|
+
end.should_not change(MList::Thread, :count)
|
89
|
+
new_message = MList::Message.last
|
90
|
+
new_message.subject.should == "Re: Test"
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should not associate a posting to a parent if not reply' do
|
94
|
+
@mail_list.process_email(MList::Email.new(:tmail => tmail_fixture('single_list')), @subscriber_one)
|
95
|
+
lambda do
|
96
|
+
lambda do
|
97
|
+
@mail_list.post(
|
98
|
+
:subscriber => @subscriber_one,
|
99
|
+
:subject => 'Test',
|
100
|
+
:text => 'It is up to the application to provide reply_to'
|
101
|
+
)
|
102
|
+
end.should change(MList::Message, :count).by(1)
|
103
|
+
end.should change(MList::Thread, :count).by(1)
|
104
|
+
message = MList::Message.last
|
105
|
+
message.parent.should be_nil
|
106
|
+
message.parent_identifier.should be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should capture the message-id of delivered email' do
|
110
|
+
message = @mail_list.post(
|
111
|
+
:subscriber => @subscriber_one,
|
112
|
+
:subject => 'Test',
|
113
|
+
:text => 'Email must have a message id for threading')
|
114
|
+
message.reload.identifier.should_not be_nil
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should copy the subscriber if desired' do
|
118
|
+
@mail_list.post(
|
119
|
+
:subscriber => @subscriber_one,
|
120
|
+
:subject => 'Copy Me',
|
121
|
+
:text => 'Email should be sent to subscriber if desired',
|
122
|
+
:copy_sender => true)
|
123
|
+
|
124
|
+
tmail = @outgoing_server.deliveries.last
|
125
|
+
tmail.bcc.should include(@subscriber_one.email_address)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should not copy the subscriber if undesired and list includes the subscriber' do
|
129
|
+
# The MList::List implementor may include the sending subscriber
|
130
|
+
stub(@manager_list).recipients {[@subscriber_one, @subscriber_two]}
|
131
|
+
|
132
|
+
@mail_list.post(
|
133
|
+
:subscriber => @subscriber_one,
|
134
|
+
:subject => 'Do Not Copy Me',
|
135
|
+
:text => 'Email should not be sent to subscriber if undesired',
|
136
|
+
:copy_sender => false)
|
137
|
+
|
138
|
+
tmail = @outgoing_server.deliveries.last
|
139
|
+
tmail.bcc.should_not include(@subscriber_one.email_address)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'message storage' do
|
144
|
+
def process_post
|
145
|
+
@mail_list.process_email(MList::Email.new(:tmail => @post_tmail), @subscriber)
|
146
|
+
MList::Message.last
|
147
|
+
end
|
148
|
+
|
149
|
+
before do
|
150
|
+
@post_tmail = tmail_fixture('single_list')
|
151
|
+
@subscriber = @subscriber_one
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should not include list label in subject' do
|
155
|
+
@post_tmail.subject = '[Discussions] Test'
|
156
|
+
process_post.subject.should == 'Test'
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should not include list label in reply subject' do
|
160
|
+
@post_tmail.subject = 'Re: [Discussions] Test'
|
161
|
+
process_post.subject.should == 'Re: Test'
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should not bother labels it does not understand in subject' do
|
165
|
+
@post_tmail.subject = '[Ann] Test'
|
166
|
+
process_post.subject.should == '[Ann] Test'
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should not bother labels it does not understand in reply subject' do
|
170
|
+
@post_tmail.subject = 'Re: [Ann] Test'
|
171
|
+
process_post.subject.should == 'Re: [Ann] Test'
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should be careful of multiple re:' do
|
175
|
+
@post_tmail.subject = 'Re: [Ann] RE: Test'
|
176
|
+
process_post.subject.should == 'Re: [Ann] Test'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe 'finding parent message' do
|
181
|
+
def email(path)
|
182
|
+
MList::Email.new(:tmail => tmail_fixture(path))
|
183
|
+
end
|
184
|
+
|
185
|
+
before do
|
186
|
+
@parent_message = MList::Message.new
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should be nil if none found' do
|
190
|
+
do_not_call(@mail_list.messages).find
|
191
|
+
@mail_list.find_parent_message(email('single_list')).should be_nil
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should use in-reply-to field when present' do
|
195
|
+
mock(@mail_list.messages).find(:first, :conditions => [
|
196
|
+
'identifier = ?', 'F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative.com'
|
197
|
+
]) { @parent_message }
|
198
|
+
@mail_list.find_parent_message(email('single_list_reply')).should == @parent_message
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should be references field if present and no in-reply-to' do
|
202
|
+
tmail = tmail_fixture('single_list_reply')
|
203
|
+
tmail['in-reply-to'] = nil
|
204
|
+
mock(@mail_list.messages).find(:first,
|
205
|
+
:conditions => ['identifier in (?)', ['F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative.com']],
|
206
|
+
:order => 'created_at desc') { @parent_message }
|
207
|
+
@mail_list.find_parent_message(MList::Email.new(:tmail => tmail)).should == @parent_message
|
208
|
+
end
|
209
|
+
|
210
|
+
describe 'by subject' do
|
211
|
+
def search_subject(subject = nil)
|
212
|
+
simple_matcher("search by the subject '#{subject}'") do |email|
|
213
|
+
if subject
|
214
|
+
mock(@mail_list.messages).find(
|
215
|
+
:first,
|
216
|
+
:conditions => ['subject = ?', subject],
|
217
|
+
:order => 'created_at asc'
|
218
|
+
) {@parent_message}
|
219
|
+
else
|
220
|
+
do_not_call(@mail_list.messages).find
|
221
|
+
end
|
222
|
+
@mail_list.find_parent_message(email)
|
223
|
+
!subject.nil?
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
before do
|
228
|
+
@parent_message = MList::Message.new
|
229
|
+
@mail_list = MList::MailList.new
|
230
|
+
stub(@mail_list).label { 'list name' }
|
231
|
+
@reply_tmail = tmail_fixture('single_list')
|
232
|
+
@reply_email = MList::Email.new(:tmail => @reply_tmail)
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should be employed if it has "re:" in it' do
|
236
|
+
@reply_tmail.subject = "Re: Test"
|
237
|
+
@reply_email.should search_subject('Test')
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should not be employed when no "re:"' do
|
241
|
+
@reply_email.should_not search_subject
|
242
|
+
end
|
243
|
+
|
244
|
+
['RE: [list name] Re: Test', 'Re: [list name] Re: [list name] Test', '[list name] Re: Test'].each do |subject|
|
245
|
+
it "should handle '#{subject}'" do
|
246
|
+
@reply_tmail.subject = subject
|
247
|
+
@reply_email.should search_subject('Test')
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe 'delivery' do
|
254
|
+
include MList::Util::EmailHelpers
|
255
|
+
|
256
|
+
def process_post
|
257
|
+
@mail_list.process_email(MList::Email.new(:source => @post_tmail.to_s), @subscriber)
|
258
|
+
@outgoing_server.deliveries.last
|
259
|
+
end
|
260
|
+
|
261
|
+
before do
|
262
|
+
@post_tmail = tmail_fixture('single_list')
|
263
|
+
@subscriber = @subscriber_one
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'should be blind copied to recipients' do
|
267
|
+
mock.proxy(@mail_list.messages).build(anything) do |message|
|
268
|
+
mock(message.delivery).bcc=(%w(john@example.com))
|
269
|
+
message
|
270
|
+
end
|
271
|
+
process_post
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'should not deliver to addresses found in the to header' do
|
275
|
+
@post_tmail.to = ['john@example.com', 'list_one@example.com']
|
276
|
+
mock.proxy(@mail_list.messages).build(anything) do |message|
|
277
|
+
mock(message.delivery).bcc=([])
|
278
|
+
message
|
279
|
+
end
|
280
|
+
process_post
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'should not deliver to addresses found in the cc header' do
|
284
|
+
@post_tmail.cc = ['john@example.com']
|
285
|
+
mock.proxy(@mail_list.messages).build(anything) do |message|
|
286
|
+
mock(message.delivery).bcc=([])
|
287
|
+
message
|
288
|
+
end
|
289
|
+
process_post
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'should use list address as reply-to by default' do
|
293
|
+
process_post.should have_header('reply-to', 'Discussions <list_one@example.com>')
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'should use subscriber address as reply-to if list says to not use address' do
|
297
|
+
mock(@manager_list).reply_to_list? { false }
|
298
|
+
process_post.should have_header('reply-to', 'adam@nomail.net')
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'should use the reply-to already in an email - should not override it' do
|
302
|
+
@post_tmail['reply-to'] = 'theotheradam@nomail.net'
|
303
|
+
process_post.should have_header('reply-to', 'theotheradam@nomail.net')
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'should set x-beenthere on emails it delivers to keep from re-posting them' do
|
307
|
+
process_post.should have_header('x-beenthere', 'list_one@example.com')
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'should not remove any existing x-beenthere headers' do
|
311
|
+
@post_tmail['x-beenthere'] = 'somewhere@nomail.net'
|
312
|
+
process_post.should have_header('x-beenthere', %w(list_one@example.com somewhere@nomail.net))
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'should not modify existing headers' do
|
316
|
+
@post_tmail['x-something-custom'] = 'existing'
|
317
|
+
process_post.should have_header('x-something-custom', 'existing')
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should delete Return-Receipt-To headers since they cause clients to spam the list (the sender address)' do
|
321
|
+
@post_tmail['return-receipt-to'] = 'somewhere@nomail.net'
|
322
|
+
process_post.should_not have_header('return-receipt-to')
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'should not have any cc addresses' do
|
326
|
+
@post_tmail['cc'] = 'billybob@anywhere.com'
|
327
|
+
process_post.should_not have_header('cc')
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'should prefix the list label to the subject of messages' do
|
331
|
+
process_post.subject.should == '[Discussions] Test'
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'should move the list label to the front of subjects that already include the label' do
|
335
|
+
@post_tmail.subject = 'Re: [Discussions] Test'
|
336
|
+
process_post.subject.should == 'Re: [Discussions] Test'
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should remove multiple occurrences of Re:' do
|
340
|
+
@post_tmail.subject = 'Re: [Discussions] Re: Test'
|
341
|
+
process_post.subject.should == 'Re: [Discussions] Test'
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'should remove DomainKey-Signature headers so that we can sign the redistribution' do
|
345
|
+
@post_tmail['DomainKey-Signature'] = "a whole bunch of junk"
|
346
|
+
process_post.should_not have_header('domainkey-signature')
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'should remove DKIM-Signature headers so that we can sign the redistribution' do
|
350
|
+
@post_tmail['DKIM-Signature'] = "a whole bunch of junk"
|
351
|
+
process_post.should_not have_header('dkim-signature')
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'should capture the new message-ids' do
|
355
|
+
delivered = process_post
|
356
|
+
delivered.header_string('message-id').should_not be_blank
|
357
|
+
MList::Message.last.identifier.should == remove_brackets(delivered.header_string('message-id'))
|
358
|
+
delivered.header_string('message-id').should_not match(/F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative\.com/)
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'should maintain the content-id part headers (inline images, etc)' do
|
362
|
+
@post_tmail = tmail_fixture('embedded_content')
|
363
|
+
process_post.parts[1].parts[1]['content-id'].to_s.should == "<CF68EC17-F8ED-478A-A4A1-AEBF165A8830/bg_pattern.jpg>"
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'should add standard list headers when they are available' do
|
367
|
+
stub(@manager_list).help_url {'http://list_manager.example.com/help'}
|
368
|
+
stub(@manager_list).subscribe_url {'http://list_manager.example.com/subscribe'}
|
369
|
+
stub(@manager_list).unsubscribe_url {'http://list_manager.example.com/unsubscribe'}
|
370
|
+
stub(@manager_list).owner_url {"<mailto:list_manager@example.com>\n(Jimmy Fish)"}
|
371
|
+
stub(@manager_list).archive_url {'http://list_manager.example.com/archive'}
|
372
|
+
|
373
|
+
tmail = process_post
|
374
|
+
tmail.should have_headers(
|
375
|
+
'list-id' => "<list_one@example.com>",
|
376
|
+
'list-help' => "<http://list_manager.example.com/help>",
|
377
|
+
'list-subscribe' => "<http://list_manager.example.com/subscribe>",
|
378
|
+
'list-unsubscribe' => "<http://list_manager.example.com/unsubscribe>",
|
379
|
+
'list-post' => "<list_one@example.com>",
|
380
|
+
'list-owner' => '<mailto:list_manager@example.com>(Jimmy Fish)',
|
381
|
+
'list-archive' => "<http://list_manager.example.com/archive>",
|
382
|
+
'errors-to' => '"Discussions" <mlist-list_one@example.com>',
|
383
|
+
# I couldn't get tmail to quote 'Discussions', so apostrophe's would break smtp
|
384
|
+
'sender' => 'mlist-list_one@example.com'
|
385
|
+
)
|
386
|
+
tmail.header_string('x-mlist-version').should =~ /\d+\.\d+\.\d+/
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'should not add list headers that are not available or nil' do
|
390
|
+
stub(@manager_list).help_url {nil}
|
391
|
+
delivery = process_post
|
392
|
+
delivery.should_not have_header('list-help')
|
393
|
+
delivery.should_not have_header('list-subscribe')
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'should append the list footer to text/plain emails' do
|
397
|
+
@post_tmail.body = "My Email\n\n\n\n\n"
|
398
|
+
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
399
|
+
process_post.body.should == "My Email\n\n\n\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'should append the list footer to multipart/alternative, text/plain part of emails' do
|
403
|
+
@post_tmail = tmail_fixture('content_types/multipart_alternative_simple')
|
404
|
+
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
405
|
+
process_post.parts[0].body.should match(/#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}/)
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'should handle whitespace well when appending footer' do
|
409
|
+
@post_tmail.body = "My Email"
|
410
|
+
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
411
|
+
process_post.body.should == "My Email\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'should strip out any existing footers from the list' do
|
415
|
+
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
416
|
+
@post_tmail.body = %{My Email
|
417
|
+
|
418
|
+
> > #{MList::MailList::FOOTER_BLOCK_START}
|
419
|
+
> > content at front shouldn't matter
|
420
|
+
> > #{MList::MailList::FOOTER_BLOCK_END}
|
421
|
+
|
422
|
+
>> #{MList::MailList::FOOTER_BLOCK_START}
|
423
|
+
>> this is fine to be removed
|
424
|
+
>> #{MList::MailList::FOOTER_BLOCK_END}
|
425
|
+
|
426
|
+
#{MList::MailList::FOOTER_BLOCK_START}
|
427
|
+
this is without any in front
|
428
|
+
#{MList::MailList::FOOTER_BLOCK_END}
|
429
|
+
}
|
430
|
+
process_post.body.should == "My Email\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
|
431
|
+
end
|
432
|
+
|
433
|
+
describe 'time' do
|
434
|
+
include TMail::TextUtils
|
435
|
+
|
436
|
+
before do
|
437
|
+
@old_zone_default = Time.zone_default
|
438
|
+
@system_time = Time.parse('Thu, 2 Apr 2009 15:22:04')
|
439
|
+
mock(Time).now.times(any_times) { @system_time }
|
440
|
+
end
|
441
|
+
|
442
|
+
after do
|
443
|
+
Time.zone_default = @old_zone_default
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'should keep date of email post' do
|
447
|
+
@post_tmail['date'] = 'Thu, 2 Apr 2009 15:22:04 -0400'
|
448
|
+
process_post.header_string('date').should == 'Thu, 2 Apr 2009 15:22:04 -0400'
|
449
|
+
end
|
450
|
+
|
451
|
+
it 'should store the delivery time as created_at of message record' do
|
452
|
+
Time.zone_default = 'Pacific Time (US & Canada)'
|
453
|
+
@post_tmail['date'] = 'Wed, 1 Apr 2009 15:22:04 -0400'
|
454
|
+
process_post.header_string('date').should == 'Wed, 1 Apr 2009 15:22:04 -0400'
|
455
|
+
MList::Message.last.created_at.should == @system_time
|
456
|
+
end
|
457
|
+
|
458
|
+
# I think that what TMail is doing is evil, but it's reference to
|
459
|
+
# a ruby-talk discussion leads to Japanese, which I cannot read.
|
460
|
+
# I'd prefer that it leave the problem of timezones up to the client,
|
461
|
+
# especially since ActiveSupport does an EXCELLENT job of making
|
462
|
+
# time zones not hurt so much.
|
463
|
+
it 'should use the Time.now (zone of the machine) for date header' do
|
464
|
+
@post_tmail['date'] = nil
|
465
|
+
process_post.header_string('date').should == time2str(@system_time)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|